Compare commits

..
Author SHA1 Message Date
Hut512 34bc7ca76d Simplify NoClipCommand 2026-09-22 01:03:43 +02:00
Hut512 f92ff2177b Remove netty Cursor render 2026-09-22 00:59:47 +02:00
Hut512 06266a0dba Remove obsolete AntiNocom 2026-09-22 00:59:47 +02:00
Hut512 cb6906e2ec Make Smart Place thread safe 2026-09-22 00:58:36 +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
139 changed files with 4870 additions and 1465 deletions
+5
View File
@@ -15,6 +15,11 @@ bin/
.vscode
.settings
# Language Server
**/.project
**/.factorypath
**/.classpath
# Other
lib
/WebsiteBackend/data
@@ -35,6 +35,7 @@ dependencies {
compileOnly(libs.classindex)
annotationProcessor(libs.classindex)
compileOnly(project(":SpigotCore", "default"))
compileOnly(project(":KotlinCore", "default"))
compileOnly(libs.axiom)
compileOnly(libs.authlib)
@@ -32,6 +32,7 @@ import de.steamwar.bausystem.features.tracer.TraceManager;
import de.steamwar.bausystem.features.tracer.TraceRecorder;
import de.steamwar.bausystem.features.world.BauScoreboard;
import de.steamwar.bausystem.linkage.BauGuiItem;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionSystem;
import de.steamwar.bausystem.utils.ScoreboardElement;
import de.steamwar.bausystem.utils.TickManager;
@@ -40,8 +41,9 @@ import de.steamwar.command.AbstractValidator;
import de.steamwar.command.SWCommandUtils;
import de.steamwar.core.CRIUSleepEvent;
import de.steamwar.core.Core;
import de.steamwar.core.WorldEditRendererCUIEditor;
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.SpigotLinker;
import de.steamwar.message.Message;
@@ -49,6 +51,7 @@ import de.steamwar.providers.BauServerInfo;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.GameRule;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -129,7 +132,19 @@ public class BauSystem extends JavaPlugin implements Listener {
TraceManager.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);
@@ -290,7 +290,7 @@ public class KillcheckerVisualizer {
}
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());
rEntities.put(point, entity);
if (outlinePoints.contains(point)) outlinePointsCache.add(point);
@@ -46,7 +46,7 @@ public class ObserverTracerListener implements Listener {
public ObserverTracerListener() {
Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> {
SWPlayer.allWithSingleComponent(ObserverTracer.class).forEach(pair -> {
if (pair.getPlayer().getGameMode() != GameMode.SPECTATOR) return;
if (pair.getSwPlayer().getPlayer().getGameMode() != GameMode.SPECTATOR) return;
pair.getComponent().show();
});
}, 15L, 15L);
@@ -185,17 +185,16 @@ public class TestblockCommand extends SWCommand {
return new TypeMapper<SchematicNode>() {
@Override
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.get(((Player) commandSender).getUniqueId()), s));
return stringList;
}
@Override
public SchematicNode map(CommandSender commandSender, PreviousArguments previousArguments, String s) {
SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s);
if (node == null) {
node = SchematicNode.getNodeFromPath(SteamwarUser.byId(0), s);
}
SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.byId(0), s);
if (node == null) node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s);
return node;
}
};
@@ -19,6 +19,7 @@
package de.steamwar.bausystem.features.simulator;
import com.destroystokyo.paper.event.server.ServerTickEndEvent;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.SWUtils;
@@ -77,8 +78,16 @@ public class SimulatorCursor implements Listener {
return isSimulatorItem(player.getInventory().getItemInMainHand()) || isSimulatorItem(player.getInventory().getItemInOffHand());
}
private static Set<Player> scheduledUpdates = new HashSet<>();
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
@@ -122,6 +131,7 @@ public class SimulatorCursor implements Listener {
public void onPlayerQuit(PlayerQuitEvent event) {
cursorType.remove(event.getPlayer());
removeCursor(event.getPlayer());
scheduledUpdates.remove(event.getPlayer());
}
private static final Map<Player, Long> LAST_SNEAKS = new HashMap<>();
@@ -19,12 +19,10 @@
package de.steamwar.bausystem.features.smartplace;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
@@ -34,104 +32,98 @@ import org.bukkit.block.data.*;
import org.bukkit.block.data.type.*;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.RayTraceResult;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@Linked
public class SmartPlaceListener implements Listener {
private static final Set<Material> CONTAINERS = new HashSet<>();
private static final Set<Material> IGNORED = new HashSet<>();
private static final Set<Material> IGNORED = Set.of(Material.TNT, Material.REDSTONE_ORE, Material.BEEHIVE, Material.SEA_PICKLE);
static {
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);
private static final Set<UUID> FAKED_SNEAK = new HashSet<>();
IGNORED.add(Material.TNT);
IGNORED.add(Material.REDSTONE_ORE);
IGNORED.add(Material.BEEHIVE);
IGNORED.add(Material.SEA_PICKLE);
IGNORED.remove(Material.STONE);
IGNORED.remove(Material.BARRIER);
private static boolean isContainer(Block block) {
if (block.getType() == Material.COMPARATOR) return false;
if (block.getType() == Material.GRINDSTONE) return true;
return block.getState() instanceof TileState || block.getBlockData() instanceof Stairs;
}
private static final Set<Player> SMART_PLACING = new HashSet<>();
private static final Set<Player> WAS_EXECUTED = new HashSet<>();
private static boolean isReallySneaking(Player player) {
return player.isSneaking() && !FAKED_SNEAK.contains(player.getUniqueId());
}
public SmartPlaceListener() {
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (player, packet) -> {
if (!Permission.BUILD.hasPermission(player)) return packet;
if (!Config.getInstance().get(player).getPlainValueOrDefault("smartPlace", false)) return packet;
RayTraceResult rayTraceResult = player.rayTraceBlocks(6);
Block block = rayTraceResult != null ? rayTraceResult.getHitBlock() : null;
BlockFace blockFace = rayTraceResult != null ? rayTraceResult.getHitBlockFace() : null;
boolean shouldSneak = false;
if (block != null) {
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;
private static boolean shouldSmartPlace(Block block, BlockFace blockFace, Material heldType) {
boolean smartPlace = false;
if (block.getType().isInteractable() || block.getType() == Material.NOTE_BLOCK) {
smartPlace = true;
}
if (isContainer(block)) {
if (heldType == Material.TNT) {
if (block.getType() == Material.CHEST || block.getType() == Material.BARREL || block.getType().name().endsWith("SHULKER_BOX")) {
smartPlace = false;
}
}
boolean sneaking = player.isSneaking();
if (sneaking) SMART_PLACING.add(player);
player.setSneaking(shouldSneak || sneaking);
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
SMART_PLACING.remove(player);
player.setSneaking(sneaking);
}, 0);
return packet;
} else {
smartPlace = false;
}
if (IGNORED.contains(block.getType())) {
smartPlace = false;
}
if (blockFace == BlockFace.DOWN && (block.getType() == Material.REPEATER || block.getType() == Material.COMPARATOR) && heldType.isSolid()) {
smartPlace = true;
}
return smartPlace;
}
private static boolean smartPlaceEnabled(Player player) {
return Config.getInstance().get(player).getPlainValueOrDefault("smartPlace", false);
}
@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
public void onPlayerInteract(PlayerInteractEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("smartPlace", false)) return;
WAS_EXECUTED.add(event.getPlayer());
if (!smartPlaceEnabled(event.getPlayer())) return;
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) 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.getItem() == null || event.getItem().getType() == Material.REPEATER)) {
Repeater repeater = (Repeater) event.getClickedBlock().getBlockData();
@@ -146,9 +138,9 @@ public class SmartPlaceListener implements Listener {
@EventHandler
public void onBlockPlace(BlockPlaceEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("smartPlace", false)) return;
if (!SMART_PLACING.contains(event.getPlayer())) {
if (CONTAINERS.contains(event.getBlockAgainst().getType())) {
if (!smartPlaceEnabled(event.getPlayer())) return;
if (!isReallySneaking(event.getPlayer())) {
if (isContainer(event.getBlockAgainst())) {
SoundGroup soundGroup = event.getBlockPlaced().getBlockData().getSoundGroup();
event.getPlayer().playSound(event.getBlockPlaced().getLocation(), soundGroup.getPlaceSound(), soundGroup.getVolume() * 0.8F, soundGroup.getPitch() * 0.8F);
}
@@ -165,6 +165,7 @@ public class TPSSystem implements Listener {
}
@Register(value = "default", description = "TPSLIMIT_DEFAULT_HELP")
@Register(value = "rate")
public void reset(@Validator Player player) {
TickManager.impl.setTickRate(20.0F);
sendTickRateChange();
@@ -187,15 +188,18 @@ public class TPSSystem implements Listener {
}
@Register(value = {"rate", "0"}, description = "TICK_FREEZE_HELP")
@Register(value = "freeze", description = "TICK_FREEZE_HELP_2")
public void freeze(@Validator Player player) {
public void rate(@Validator Player player) {
TickManager.impl.setFreeze(true);
sendTickRateChange();
}
@Register(value = "unfreeze", description = "TICK_UNFREEZE_HELP")
public void unfreeze(@Validator Player player) {
TickManager.impl.setTickRate(20.0F);
@Register(value = "freeze", description = "TICK_FREEZE_HELP_2")
public void freezeToggle(@Validator Player player) {
if (TickManager.impl.isFrozen()) {
TickManager.impl.setTickRate(20.0F);
} else {
TickManager.impl.setFreeze(true);
}
sendTickRateChange();
}
}
@@ -153,7 +153,11 @@ public class TraceCommand extends SWCommand {
@Register(value = "isolate", description = "TRACE_COMMAND_HELP_ISOLATE")
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);
}
@@ -347,9 +347,10 @@ public class TraceManager implements Listener {
* Toggles the isolated render for the given records and player
*
* @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);
Region region = Region.getRegion(player.getLocation());
@@ -374,12 +375,20 @@ public class TraceManager implements Listener {
isolateFlag.toggleId(record.getTntId());
}
if (isolateFlag.isEmpty() && playerTraceShowData.hasViewFlagOnly(IsolateFlag.class) && records.length != 0) {
playerTraceShowData.removeViewFlag(IsolateFlag.class);
}
PlayerTraceShowData finalPlayerTraceShowData = playerTraceShowData;
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
trace.render(player, finalPlayerTraceShowData);
followerMap.getOrDefault(player, Collections.emptySet()).forEach(follower -> {
trace.render(follower, finalPlayerTraceShowData);
});
if (trace.getUuid() == ptrace.getUuid() || finalPlayerTraceShowData.hasNoViewFlags()) {
trace.render(player, 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();
while (iter.hasNext()) {
TNTPrimed tnt = iter.next();
if (tnt.isDead()) {
iter.remove();
tntSpawnRegion.remove(tnt);
historyMap.remove(tnt);
tntSpawnRegion.remove(tnt);
continue;
}
if (tnt.getFuseTicks() == 80) continue;
TNTPoint record = record(tnt, wrappedTrace, Collections.emptyList());
if (record == null) {
@@ -95,4 +95,8 @@ public class PlayerTraceShowData {
public void addViewFlag(ViewFlag viewFlag) {
viewFlags.put(viewFlag.getClass(), viewFlag);
}
public <T extends ViewFlag> void removeViewFlag(Class<T> clazz) {
viewFlags.remove(clazz);
}
}
@@ -48,9 +48,9 @@ import static de.steamwar.bausystem.features.util.TNTClickListener.TNT_CLICK_DET
*/
public class TraceEntity extends RBlockDisplay {
private static final float TNT_VISUAL_SCALE = 0.98F;
private static final float TNT_VISUAL_OFFSET = -TNT_VISUAL_SCALE / 2.0F;
private static final Transformation TNT_VISUAL_TRANSFORM = new Transformation(
public static final float TNT_VISUAL_SCALE = 0.98F;
public static final float TNT_VISUAL_OFFSET = -TNT_VISUAL_SCALE / 2.0F;
public static final Transformation TNT_VISUAL_TRANSFORM = new Transformation(
new Vector3f(TNT_VISUAL_OFFSET, 0.0F, TNT_VISUAL_OFFSET),
new Quaternionf(0, 0, 0, 1),
new Vector3f(TNT_VISUAL_SCALE, TNT_VISUAL_SCALE, TNT_VISUAL_SCALE),
@@ -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) {
RBlockDisplay y = new RBlockDisplay(server, yLocation);
y.setBlock(Material.WHITE_STAINED_GLASS.createBlockData());
y.setTransform(TraceEntity.TNT_VISUAL_TRANSFORM);
}
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) {
RBlockDisplay second = new RBlockDisplay(server, secoundLocation);
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
public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
if (tntToIsolate.isEmpty()) return records;
@@ -22,129 +22,79 @@ package de.steamwar.bausystem.features.util;
import com.comphenix.tinyprotocol.TinyProtocol;
import com.mojang.authlib.GameProfile;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.tpslimit.TPSUtils;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
import de.steamwar.command.SWCommand;
import de.steamwar.core.ProtocolWrapper;
import de.steamwar.core.SWPlayer;
import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.entity.player.Abilities;
import net.minecraft.world.level.GameType;
import org.bukkit.Bukkit;
import io.papermc.paper.event.player.PlayerFailMoveEvent;
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
import org.bukkit.GameMode;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockCanBuildEvent;
import org.bukkit.event.player.PlayerGameModeChangeEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerToggleFlightEvent;
import java.util.function.BiFunction;
@Linked
public class NoClipCommand extends SWCommand implements Listener {
public static class NoClipData implements SWPlayer.Component {
private long lastTick = -1;
@Override
public void onUnmount(SWPlayer player) {
player.setGameMode(GameMode.CREATIVE);
public void onUnmount(SWPlayer swPlayer) {
Player player = swPlayer.getPlayer();
showClientGameMode(player, GameMode.CREATIVE, GameMode.CREATIVE);
}
}
public NoClipCommand() {
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) {
((CraftPlayer) player).getHandle().gameMode.gameModeForPlayer = GameType.byId(gameMode.getValue());
}
@Register(help = true)
@Register
public void genericCommand(@Validator Player player) {
SWPlayer swPlayer = SWPlayer.of(player);
if (swPlayer.hasComponent(NoClipData.class)) {
swPlayer.removeComponent(NoClipData.class);
} else {
player.setGameMode(GameMode.SPECTATOR);
Abilities abilities = (((CraftPlayer) player).getHandle()).getAbilities();
abilities.mayBuild = true;
abilities.mayfly = true;
player.setGameMode(GameMode.CREATIVE);
player.setAllowFlight(true);
player.setFlying(true);
swPlayer.setComponent(new NoClipData());
BauSystem.MESSAGE.send("OTHER_NOCLIP_SLOT_INFO", player);
TinyProtocol.instance.sendPacket(player, new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, 1F));
pseudoGameMode(player, GameMode.SPECTATOR);
showClientGameMode(player, GameMode.CREATIVE, GameMode.SPECTATOR);
}
}
@EventHandler
public void onBauMemberUpdate(BauMemberUpdateEvent event) {
event.getNewSpectator().forEach(player -> {
SWPlayer.of(player).removeComponent(NoClipData.class);
});
public void onPlayerFailMove(PlayerFailMoveEvent event) {
if (event.getFailReason() != PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK) return;
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) {
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
event.setCancelled(true);
}
}
@EventHandler(ignoreCancelled = true)
@EventHandler
public void onBlock(BlockCanBuildEvent event) {
if (event.getPlayer() == null) return;
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
event.setBuildable(true);
}
}
@EventHandler(ignoreCancelled = true)
@EventHandler
public void onPlayerToggleFlight(PlayerToggleFlightEvent event) {
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
event.setCancelled(true);
@@ -152,20 +102,13 @@ public class NoClipCommand extends SWCommand implements Listener {
}
}
@EventHandler(ignoreCancelled = true)
public void onPlayerTeleport(PlayerTeleportEvent event) {
if (event.getCause() != PlayerTeleportEvent.TeleportCause.SPECTATE) {
return;
}
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
event.setCancelled(true);
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
event.getPlayer().setSpectatorTarget(null);
}, 1L);
}
/**
* @param player player
* @param localMode GameMode for breaking and placing
* @param listedMode GameMode for noPhysics
*/
private static void showClientGameMode(Player player, GameMode localMode, GameMode listedMode) {
TinyProtocol.instance.sendPacket(player, new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, localMode.getValue()));
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.GAMEMODE, new GameProfile(player.getUniqueId(), player.getName()), listedMode));
}
private static void pseudoGameMode(Player player, GameMode gameMode) {
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.GAMEMODE, new GameProfile(player.getUniqueId(), player.getName()), gameMode));
}
}
}
@@ -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
public void onPlayerQuit(PlayerQuitEvent event) {
SWScoreboard.impl.removeScoreboard(event.getPlayer());
ADDITIONAL_SCOREBOARD_LINES.remove(event.getPlayer());
}
}
@@ -325,7 +325,7 @@ public class FixedRegion implements Region {
}
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 baseFile = new File(BauSystem.getInstance().getDataFolder().getParentFile(), "FightSystem");
+1
View File
@@ -37,5 +37,6 @@ tasks.register<DevServer>("DevBau21") {
dependsOn(":SpigotCore:shadowJar")
dependsOn(":BauSystem:shadowJar")
dependsOn(":SchematicSystem:shadowJar")
dependsOn(":KotlinCore:shadowJar")
template = "Bau21"
}
+18 -5
View File
@@ -7,6 +7,7 @@ import com.github.ajalt.mordant.rendering.TextColors
import com.github.ajalt.mordant.rendering.TextStyles
import de.steamwar.db.Database
import de.steamwar.db.execute
import de.steamwar.db.executeScript
import de.steamwar.db.useDb
import java.io.File
@@ -22,13 +23,25 @@ class ResetCommand : CliktCommand() {
val schema = schemaFile.readText()
val tables = execute("SHOW TABLES;") { it.getString(1) }
for (table in tables) {
execute("DROP TABLE IF EXISTS $table;") { }
execute("SET FOREIGN_KEY_CHECKS=0;") { }
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!")))
}
}
}
+28 -5
View File
@@ -108,8 +108,10 @@ class DevCommand : CliktCommand("dev") {
if (!templateFile.exists()) {
throw CliktError("Could not find world template: ${templateFile.absolutePath}")
}
worldFile.parentFile?.mkdirs()
templateFile.copyRecursively(worldFile)
}
val worldDir = worldFile.parentFile ?: workingDir
val devFile = File("/configs/DevServer/${System.getProperty("user.name")}.$port.$version")
if (System.getProperty("user.name") != "minecraft") {
@@ -120,10 +122,11 @@ class DevCommand : CliktCommand("dev") {
args, jvmArgs, listOf(
jarFile,
*(if (forceUpgrade) arrayOf("-forceUpgrade") else arrayOf()),
"--log-strip-color",
"--port", port.toString(),
"--level-name", worldFile.name,
"--world-dir", workingDir.absolutePath,
"--nogui",
"--world-dir", worldDir.absolutePath,
"nogui",
*(if (plugins != null) arrayOf("--plugins", plugins!!.absolutePathString()) else arrayOf())
), serverDir
)
@@ -156,6 +159,16 @@ class DevCommand : CliktCommand("dev") {
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(
8 to "/jars/paper-1.8.8.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) {
val effectiveJvmArgs = mutableListOf<String>()
effectiveJvmArgs += jvmArgs
if (!isVelocity(server)) {
extendedStartupParams.forEach { arg ->
if (effectiveJvmArgs.none { it == arg }) {
effectiveJvmArgs += arg
}
}
}
val process = ProcessBuilder(
jvm?.absolutePath
?: if (isJava8(server)) "/usr/lib/jvm/openj9-8/bin/java" else "java",
*jvmArgs.toTypedArray(),
?: if (isJava8(server)) "/usr/lib/jvm/openj9-8/bin/java" else "/usr/lib/jvm/openj9-21/bin/java",
*effectiveJvmArgs.toTypedArray(),
*args.toTypedArray(),
*jvmDefaultParams,
*(if (isJava8(server)) arrayOf() else jvmArgOverrides),
*(if (isJava8(server)) arrayOf() else jvmNonJava8Params),
*(if (profile) arrayOf("-javaagent:/jars/LixfelsProfiler.jar=start") else arrayOf()),
"-Xshareclasses:nonfatal,name=$server",
"-jar",
+120 -9
View File
@@ -16,15 +16,14 @@ import java.sql.ResultSet
import java.util.*
object Database {
lateinit var host: String
lateinit var port: String
lateinit var database: String
val host: String
val port: String
val database: String
val username: String
val password: String
lateinit var db: Database
fun ensureConnected() {
if (::db.isInitialized) {
return
}
init {
val config = File(System.getProperty("user.home"), "mysql.properties")
if (!config.exists()) {
@@ -38,9 +37,14 @@ object Database {
host = props.getProperty("host")
port = props.getProperty("port")
database = props.getProperty("database")
username = props.getProperty("user")
password = props.getProperty("password")
}
val username = props.getProperty("user")
val password = props.getProperty("password")
fun ensureConnected() {
if (::db.isInitialized) {
return
}
val url = "jdbc:mariadb://$host:$port/$database"
@@ -79,6 +83,113 @@ fun <T> JdbcTransaction.executeSingle(sql: String, transform: (ResultSet) -> T):
}.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) {
de.steamwar.db.Database.ensureConnected()
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 {
steamwar.kotlin
kotlin("plugin.lombok")
}
kotlin {
@@ -40,4 +41,8 @@ dependencies {
compileOnlyApi(libs.exposedDao)
compileOnlyApi(libs.exposedJdbc)
compileOnlyApi(libs.exposedTime)
testImplementation(kotlin("test"))
}
repositories {
mavenCentral()
}
@@ -86,6 +86,24 @@ class CheckedSchematic(id: EntityID<CompositeID>) : CompositeEntity(id) {
useDb {
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 })
@@ -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 })
@@ -200,6 +237,7 @@ class EventFight(id: EntityID<Int>) : IntEntity(id), Comparable<EventFight> {
override fun delete() =
useDb {
EventRelation.deleteRelations(this@EventFight)
super.delete()
}
}
}
@@ -159,10 +159,13 @@ class EventGroup(id: EntityID<Int>) : IntEntity(id) {
}
override fun delete() =
useDb { super.delete() }
useDb {
EventRelation.deleteRelations(this@EventGroup)
super.delete()
}
enum class EventGroupType {
GROUP_STAGE,
ELIMINATION_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.IntIdTable
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.IntEntityClass
import org.jetbrains.exposed.v1.jdbc.deleteWhere
import org.jetbrains.exposed.v1.jdbc.select
object EventRelationTable : IntIdTable("EventRelation") {
@@ -59,6 +61,23 @@ class EventRelation(id: EntityID<Int>) : IntEntity(id) {
fun getGroupRelations(group: EventGroup) =
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
fun create(fight: EventFight, fightTeam: FightTeam, fromType: FromType, fromId: Int, fromPlace: Int) =
useDb {
@@ -159,4 +178,4 @@ class EventRelation(id: EntityID<Int>) : IntEntity(id) {
enum class FromType {
FIGHT, GROUP
}
}
}
@@ -20,9 +20,12 @@
package de.steamwar.sql
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.CompositeIdTable
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.dao.CompositeEntity
import org.jetbrains.exposed.v1.dao.CompositeEntityClass
@@ -69,6 +72,28 @@ class FightPlayer(id: EntityID<CompositeID>) : CompositeEntity(id) {
useDb {
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 })
@@ -146,12 +146,12 @@ public final class GameModeConfig<M, W> {
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.
*
* @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
@@ -246,7 +246,7 @@ public final class GameModeConfig<M, W> {
}
CheckQuestions = loader.getStringList("CheckQuestions");
Checkers = loader.getIntList("Checkers");
Checkers = loader.getIntSet("Checkers");
Times = new TimesConfig(loader.with("Times"));
// Arena would be here to be in config order but needs Schematic.Size and EnterStages loaded afterwards
Schematic = new SchematicConfig<>(loader.with("Schematic"));
@@ -488,6 +488,27 @@ public final class GameModeConfig<M, W> {
*/
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) {
loaded = loader.canLoad();
WaterDepth = loader.getInt("WaterDepth", 0);
@@ -505,6 +526,9 @@ public final class GameModeConfig<M, W> {
Leaveable = loader.getBoolean("Leaveable", false);
AllowMissiles = loader.getBoolean("AllowMissiles", !EnterStages.isEmpty());
NoFloor = loader.getBoolean("NoFloor", false);
WindchargesCanCrossMiddle = loader.getBoolean("WindchargesCanCrossMiddle", false);
WindchargesInteractWithBlocks = loader.getBoolean("WindchargesInteractWithBlocks", true);
WindchargesDestroyWater = loader.getBoolean("WindchargesDestroyWater", false);
}
@ToString
@@ -664,13 +688,6 @@ public final class GameModeConfig<M, W> {
*/
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
*
@@ -686,10 +703,12 @@ public final class GameModeConfig<M, W> {
public final double MaxDesignBlastResistance;
/**
* List of limited material (combinations)<br/>
* List contains tags Amount (integer) and Materials (List of material names in Spigot 1.12 AND Spigot 1.15 format)
* List of limited block materials
*
* @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) {
loaded = loader.canLoad();
@@ -707,32 +726,140 @@ public final class GameModeConfig<M, W> {
IgnorePublicOnly = loader.getBoolean("IgnorePublicOnly", false);
UnlimitedPrepare = loader.getBoolean("UnlimitedPrepare", false);
MaxBlocks = loader.getInt("MaxBlocks", 0);
MaxDispenserItems = loader.getInt("MaxDispenserItems", 128);
MaxBlastResistance = loader.getDouble("MaxBlastResistance", Double.MAX_VALUE);
MaxDesignBlastResistance = loader.getDouble("MaxDesignBlastResistance", MaxBlastResistance);
Map<Set<M>, Integer> Limited = new HashMap<>();
for (Map<?, ?> entry : loader.getMapList("Limited")) {
int amount = (Integer) entry.get("Amount");
Set<String> materials = new HashSet<>((List<String>) entry.get("Materials"));
if (amount == 0) {
materials.forEach(material -> {
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);
}
List<BlockRule<M>> limited = new ArrayList<>();
Set<M> blocks = new HashSet<>();
for (YMLWrapper<M, ?> limitedLoader : loader.withAsList("Limited")) {
BlockRule<M> blockRule = new BlockRule<>(limitedLoader);
blocks.addAll(blockRule.Materials);
limited.add(blockRule);
}
SQLWrapper.impl.getMaterialWithGreaterBlastResistance(MaxBlastResistance).forEach(material -> {
if (Limited.entrySet().stream().anyMatch(entry -> entry.getKey().contains(material))) return;
Limited.put(Collections.singleton((M) material), 0);
});
this.Limited = Collections.unmodifiableMap(Limited);
for (M material : (List<M>) SQLWrapper.impl.getMaterialWithGreaterBlastResistance(MaxBlastResistance)) {
if (material == null || !blocks.add(material)) continue;
limited.add(new BlockRule<>(material, 0));
}
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.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
public static final class SizeConfig {
@@ -929,6 +1056,13 @@ public final class GameModeConfig<M, W> {
*/
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
*/
@@ -940,7 +1074,10 @@ public final class GameModeConfig<M, W> {
MemberDefault = loader.getString("MemberDefault", "default");
LeaderDefault = loader.getString("LeaderDefault", "default");
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",
UserPerm.MODERATION
),
NoTeamServer(
"NOTEAMSERVER_TEAM",
"NOTEAMSERVER_PERMA",
"NOTEAMSERVER_UNTIL",
"UNNOTEAMSERVER_ERROR",
"UNNOTEAMSERVER",
UserPerm.MODERATION
),
Note("NOTE_TEAM", null, null, null, null, UserPerm.PUNISHMENTS, true);
fun isMulti() =
@@ -24,6 +24,7 @@ import de.steamwar.ImplementationProvider;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public interface SQLWrapper<M> {
SQLWrapper<?> impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLWrapperImpl");
@@ -36,5 +37,21 @@ public interface SQLWrapper<M> {
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);
}
@@ -307,12 +307,12 @@ class SteamwarUser(id: EntityID<Int>) : IntEntity(id) {
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
useDb {
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);
}
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) {
Object value = this.document.get(path);
if (value == null) return defaultValue;
@@ -139,6 +158,12 @@ final class YMLWrapper<M, W> {
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) {
List<String> list = getStringList(path);
if (list.isEmpty()) {
+18 -6
View File
@@ -64,6 +64,10 @@ Arena:
AllowMissiles: false # defaults to true if EnterStages are present otherwise 'false'
# Denotes that there is no floor for this GameMode
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:
# The size of the schematics
@@ -103,15 +107,23 @@ Schematic:
UnlimitedPrepare: false # defaults to false if missing
# Maximal amount of blocks allowed in the schematic
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
MaxDesignBlastResistance: 100000000 # defaults to Double.MAX_VALUE if missing
# List of limited material (combinations)
# List contains tags Amount (integer) and Materials (List of material names in Spigot 1.12 AND Spigot 1.15 format)
# List of limited block materials
# 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:
- Materials: [ ]
Amount: 0
- Blocks: [ DISPENSER ]
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
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> Prepare = Collections.unmodifiableSet(EnumSet.of(PREPARE));
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> AntiTest = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(TEST, CHECK)));
@@ -21,8 +21,8 @@ package de.steamwar.fightsystem;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.core.Core;
import de.steamwar.core.WorldEditRendererCUIEditor;
import de.steamwar.fightsystem.commands.TechareaCommand;
import de.steamwar.core.worldedit.SteamEdit;
import de.steamwar.core.worldedit.WorldEditRendererCUIEditor;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam;
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.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));
hullHider = new HullHider();
@@ -127,8 +127,6 @@ public class FightSystem extends JavaPlugin {
if (checkSchematicNode.isPrepared()) {
Fight.getRedTeam().setSchem(checkSchematicNode, NodeData.getRevisions(checkSchematicNode) - 1);
}
new TechareaCommand();
} else if (Config.mode == ArenaMode.PREPARE) {
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.fight.Kit;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.linkage.Linked;
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;
@Linked
public class AkCommand implements CommandExecutor {
public class AkCommand extends FightSWCommand {
public AkCommand() {
new StateDependentCommand(ArenaMode.Test, FightState.All, "ak", this);
super(ArenaMode.Test, FightState.All, "ak");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
return false;
}
Player player = (Player) sender;
if (!player.isOp()) return false;
protected boolean hasPerm(Player player, SteamwarUser user) {
return player.isOp() && user.hasPerm(UserPerm.ADMINISTRATION) && Core.getInstance() != FightSystem.getPlugin();
}
if (!SteamwarUser.get(player.getUniqueId()).hasPerm(UserPerm.ADMINISTRATION) && Core.getInstance() != FightSystem.getPlugin()) {
return false;
}
Kit.createKit(args[0], player);
return false;
@Register
public void run(@Validator Player player, String name) {
Kit.createKit(name, player);
}
}
@@ -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;
import com.google.common.collect.ImmutableList;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.linkage.Linked;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.Bukkit;
import de.steamwar.sql.SteamwarUser;
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.util.StringUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Linked
public class GamemodeCommand extends BukkitCommand {
private static final List<String> GAMEMODE_NAMES = ImmutableList.of("adventure", "creative", "survival",
"spectator");
public class GamemodeCommand extends FightSWCommand {
public GamemodeCommand() {
super("gamemode");
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);
super(ArenaMode.TestOrReferee, FightState.All, "gamemode", "gm");
}
@Override
public boolean execute(CommandSender sender, String currentAlias, String[] args) {
if (!(sender instanceof 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;
protected boolean hasPerm(Player player, SteamwarUser user) {
return Config.test() || Config.isReferee(player);
}
@SuppressWarnings("deprecation")
private GameMode createMode(String modeArg) {
try {
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;
@Register(description = "GAMEMODE_HELP")
public void execute(@Validator Player player, GameMode gameMode) {
player.setGameMode(gameMode);
}
@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;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.listener.Check;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.linkage.Linked;
import de.steamwar.sql.SchematicNode;
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;
@Linked
public class InfoCommand implements CommandExecutor {
public class InfoCommand extends FightSWCommand {
public InfoCommand() {
new StateDependentCommand(ArenaMode.All, FightState.All, "fightinfo", this);
super(ArenaMode.All, FightState.All, "fightinfo");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) return false;
Player player = (Player) sender;
if (!SteamwarUser.get(player.getUniqueId()).hasPerm(UserPerm.CHECK)) return false;
protected boolean hasPerm(Player player, SteamwarUser user) {
return Check.checkPermission(user, Config.GameModeConfig);
}
@Register
public void run(@Validator Player player) {
for (FightTeam team : Fight.teams()) {
if (!team.isLeaderless()) {
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());
}
}
return false;
}
}
@@ -21,32 +21,23 @@ package de.steamwar.fightsystem.commands;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
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;
@Linked
public class KitCommand implements CommandExecutor {
public class KitCommand extends FightSWCommand {
public KitCommand() {
new StateDependentCommand(ArenaMode.AntiReplay, FightState.Setup, "kit", this);
super(ArenaMode.AntiReplay, FightState.Setup, "kit");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
return false;
}
Player player = (Player) sender;
@Register
public void execute(Player player) {
GUI.kitSelection(player, "");
}
if (args.length != 1) {
GUI.kitSelection(player, "");
} else {
Commands.kit(player, args[0]);
}
return false;
@Register
public void execute(Player player, String query) { // TODO: TabCompletion of Kit Names!
GUI.kitSelection(player, query);
}
}
@@ -21,28 +21,18 @@ package de.steamwar.fightsystem.commands;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
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;
@Linked
public class LeaveCommand implements CommandExecutor {
public class LeaveCommand extends FightSWCommand {
public LeaveCommand() {
new StateDependentCommand(ArenaMode.AntiReplay, FightState.Setup, "leave", this);
super(ArenaMode.AntiReplay, FightState.Setup, "leave");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
return false;
}
Player player = (Player) sender;
@Register
public void execute(Player player) {
Commands.leaveTeam(player);
return false;
}
}
@@ -20,52 +20,42 @@
package de.steamwar.fightsystem.commands;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.listener.Check;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.linkage.Linked;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SchematicType;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
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;
@Linked
public class LockschemCommand implements CommandExecutor {
public class LockschemCommand extends FightSWCommand {
public LockschemCommand() {
new StateDependentCommand(ArenaMode.All, FightState.Schem, "lockschem", this);
super(ArenaMode.All, FightState.Schem, "lockschem");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) return false;
Player player = (Player) sender;
protected boolean hasPerm(Player player, SteamwarUser user) {
return Check.checkPermission(user, Config.GameModeConfig);
}
if (!SteamwarUser.get(player.getUniqueId()).hasPerm(UserPerm.CHECK)) return false;
if (args.length != 1) {
FightSystem.getMessage().sendPrefixless("LOCKSCHEM_HELP", player);
return false;
}
String teamName = args[0];
@Register(description = "LOCKSCHEM_HELP")
public void execute(@Validator Player player, String teamName) {
FightTeam fightTeam = Fight.getTeamByName(teamName);
if (fightTeam == null) {
FightSystem.getMessage().sendPrefixless("UNKNOWN_TEAM", player, ChatMessageType.ACTION_BAR);
return false;
return;
}
SchematicNode.getSchematicNode(fightTeam.getSchematic()).setSchemtype(SchematicType.Normal);
FightSystem.getMessage().sendPrefixless("LOCKSCHEM_LOCKED", player, ChatMessageType.ACTION_BAR);
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.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
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;
@Linked
public class ReadyCommand implements CommandExecutor {
public class ReadyCommand extends FightSWCommand {
public ReadyCommand() {
new StateDependentCommand(ArenaMode.AntiPrepare, FightState.PostSchemSetup, "ready", this);
super(ArenaMode.AntiPrepare, FightState.PostSchemSetup, "ready");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
return false;
}
Player player = (Player) sender;
@Register
public void execute(Player player) {
Commands.toggleReady(player);
return false;
}
}
@@ -20,35 +20,19 @@
package de.steamwar.fightsystem.commands;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
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;
@Linked
public class RemoveCommand implements CommandExecutor {
public class RemoveCommand extends FightSWCommand {
public RemoveCommand() {
new StateDependentCommand(ArenaMode.VariableTeams, FightState.Setup, "remove", this);
super(ArenaMode.VariableTeams, FightState.Setup, "remove");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
return false;
}
Player player = (Player) sender;
if (args.length != 1) {
FightSystem.getMessage().sendPrefixless("REMOVE_HELP", player);
return false;
}
Commands.kick(player, args[0]);
return false;
@Register(description = "REMOVE_HELP")
public void execute(Player player, Player teamMember) {
Commands.kick(player, teamMember.getName());
}
}
@@ -26,37 +26,28 @@ import de.steamwar.fightsystem.fight.FightPlayer;
import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.fight.JoinRequest;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
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 java.util.function.BiConsumer;
@Linked
public class RequestsCommand implements CommandExecutor {
public class RequestsCommand extends FightSWCommand {
public RequestsCommand() {
new StateDependentCommand(ArenaMode.VariableTeams, FightState.AntiSpectate, "request", this);
new StateDependentCommand(ArenaMode.VariableTeams, FightState.AntiSpectate, "requests", this);
super(ArenaMode.VariableTeams, FightState.AntiSpectate, "request", "requests");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) return false;
Player player = (Player) sender;
@Register
public void execute(Player player) {
FightPlayer fp = Fight.getFightPlayer(player);
if (fp == null || !(fp.isLeader() || fp.isLiving())) {
GUI.joinRequest(player);
return false;
return;
}
if (Commands.checkGetLeader(player) == null) return false;
if (Commands.checkGetLeader(player) == null) return;
GUI.chooseJoinRequests(player);
return false;
}
public static void onJoinRequest(Player player, JoinRequest request, BiConsumer<JoinRequest, FightTeam> handleJoinRequest) {
@@ -24,32 +24,22 @@ import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.record.PacketProcessor;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
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;
@Linked
public class SkipCommand implements CommandExecutor {
public class SkipCommand extends FightSWCommand {
public SkipCommand() {
new StateDependentCommand(ArenaMode.AntiPrepare, ArenaMode.Replay.contains(Config.mode) ? FightState.All : FightState.TeamFix, "skip", this);
super(ArenaMode.AntiPrepare, ArenaMode.Replay.contains(Config.mode) ? FightState.All : FightState.TeamFix, "skip");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
return false;
}
Player player = (Player) sender;
@Register
public void execute(Player player) {
if (PacketProcessor.isReplaying() && player.getUniqueId().equals(Fight.getBlueTeam().getDesignatedLeader())) {
PacketProcessor.currentReplay().skipToSubtitle();
} else {
Commands.toggleSkip(player);
}
return false;
}
}
@@ -21,27 +21,18 @@ package de.steamwar.fightsystem.commands;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
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;
@Linked
public class StateCommand implements CommandExecutor {
public class StateCommand extends FightSWCommand {
public StateCommand() {
new StateDependentCommand(ArenaMode.Test, FightState.All, "state", this);
super(ArenaMode.Test, FightState.All, "state");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
return false;
}
GUI.state((Player) sender);
return false;
@Register
public void execute(Player player) {
GUI.state(player);
}
}
@@ -22,26 +22,18 @@ package de.steamwar.fightsystem.commands;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
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;
@Linked
public class TBCommand implements CommandExecutor {
public class TBCommand extends FightSWCommand {
public TBCommand() {
new StateDependentCommand(ArenaMode.Check, FightState.All, "resettb", this);
super(ArenaMode.Check, FightState.All, "resettb");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
return false;
}
@Register
public void execute(Player player) {
Fight.getRedTeam().pasteSchem();
return false;
}
}
@@ -20,34 +20,20 @@ package de.steamwar.fightsystem.commands;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.linkage.Linked;
import net.minecraft.server.MinecraftServer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@Linked
public class TPSWarpCommand implements CommandExecutor {
public class TPSWarpCommand extends FightSWCommand {
public TPSWarpCommand() {
new StateDependentCommand(ArenaMode.Prepare, FightState.PostSchemSetup, "tpswarp", this);
new StateDependentCommand(ArenaMode.Prepare, FightState.PostSchemSetup, "tpslimit", this);
super(ArenaMode.Prepare, FightState.PostSchemSetup, "tpswarp", "tpslimit");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
float tps;
try {
tps = Float.parseFloat(args[0]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
FightSystem.getMessage().send("TPSWARP_HELP", sender);
return false;
}
@Register(description = "TPSWARP_HELP")
public void execute(Player player, float tps) {
MinecraftServer.getServer().tickRateManager().setTickRate(tps);
FightSystem.getMessage().broadcastActionbar("TPSWARP_SET", tps);
return false;
}
}
@@ -19,42 +19,39 @@
package de.steamwar.fightsystem.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.entity.CWireframe;
import de.steamwar.entity.REntityServer;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
import org.bukkit.Bukkit;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.OneShotStateDependent;
import de.steamwar.linkage.Linked;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class TechareaCommand extends SWCommand {
private final Map<UUID, REntityServer> servers = new HashMap<>();
@Linked
public class TechareaCommand extends FightSWCommand {
private REntityServer entityServer = new REntityServer();
public TechareaCommand() {
super("techarea");
Bukkit.getScheduler().runTaskTimer(FightSystem.getPlugin(), () -> servers.forEach((uuid, rEntityServer) -> rEntityServer.tick()), 2, 2);
}
@Register
public void genericCommand(Player player) {
if (servers.containsKey(player.getUniqueId())) {
servers.get(player.getUniqueId()).close();
} else {
super(ArenaMode.Check, FightState.All, "techarea");
new OneShotStateDependent(ArenaMode.Check, FightState.All, () -> {
if (!entityServer.getEntities().isEmpty()) return;
REntityServer server = new REntityServer();
CWireframe wireframe = new CWireframe(server);
wireframe.setPos1(Config.BlueInsetRegion.getMinLocation(Config.world));
wireframe.setPos2(Config.BlueInsetRegion.getMaxLocation(Config.world).subtract(1, 1, 1));
wireframe.setBlock(Material.RED_CONCRETE.createBlockData());
});
}
server.addPlayer(player);
servers.put(player.getUniqueId(), server);
@Register
public void genericCommand(Player player) {
if (entityServer.getPlayers().contains(player)) {
entityServer.removePlayer(player);
} else {
entityServer.addPlayer(player);
}
}
}
@@ -25,27 +25,24 @@ import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.linkage.Linked;
import de.steamwar.sql.SWException;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.logging.Level;
@Linked
public class TechhiderbugCommand implements CommandExecutor {
public class TechhiderbugCommand extends FightSWCommand {
public TechhiderbugCommand() {
new StateDependentCommand(ArenaMode.All, FightState.All, "techhiderbug", this);
super(ArenaMode.All, FightState.All, "techhiderbug");
}
@Override
public boolean onCommand(CommandSender sender, Command cmd, String alias, String[] args) {
@Register
public void execute(Player player, String... args) {
StringWriter writer = new StringWriter();
try {
@@ -76,7 +73,6 @@ public class TechhiderbugCommand implements CommandExecutor {
Bukkit.getLogger().log(Level.SEVERE, "Error while generating bug report", e);
}
SWException.log("Techhider-Bug reported by " + sender.getName() + ": " + Arrays.toString(args), writer.toString());
return false;
SWException.log("Techhider-Bug reported by " + player.getName() + ": " + Arrays.toString(args), writer.toString());
}
}
@@ -23,28 +23,20 @@ import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightWorld;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
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;
@Linked
public class WGCommand implements CommandExecutor {
public class WGCommand extends FightSWCommand {
public WGCommand() {
new StateDependentCommand(ArenaMode.Check, FightState.All, "resetwg", this);
super(ArenaMode.Check, FightState.All, "resetwg");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
return false;
}
@Register
public void execute(Player player) {
FightWorld.resetWorld();
Fight.getBlueTeam().pasteSchem();
Fight.getRedTeam().pasteSchem();
return false;
}
}
@@ -25,49 +25,34 @@ import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.linkage.Linked;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import de.steamwar.sql.SteamwarUser;
import org.bukkit.entity.Player;
@Linked
public class WinCommand implements CommandExecutor {
public class WinCommand extends FightSWCommand {
public WinCommand() {
new StateDependentCommand(ArenaMode.Event, FightState.Ingame, "win", this);
super(ArenaMode.Event, FightState.Ingame, "win");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
return false;
}
Player p = (Player) sender;
protected boolean hasPerm(Player player, SteamwarUser user) {
return Config.isReferee(player);
}
if (!Config.isReferee(p)) {
FightSystem.getMessage().sendPrefixless("NOT_FIGHTLEADER", p, ChatMessageType.ACTION_BAR);
return false;
}
if (args.length == 0) {
FightSystem.getMessage().sendPrefixless("WIN_HELP", p);
return false;
}
if (args[0].equalsIgnoreCase("tie")) {
@Register
public void execute(@Validator Player player, String teamName) {
if (teamName.equalsIgnoreCase("tie")) {
FightSystem.setSpectateState(null, "Referee", "WIN_FIGHTLEADER");
return false;
return;
}
for (FightTeam team : Fight.teams()) {
if (args[0].equalsIgnoreCase(team.getName())) {
if (teamName.equalsIgnoreCase(team.getName())) {
FightSystem.setSpectateState(team, "Referee", "WIN_FIGHTLEADER");
return false;
return;
}
}
return false;
}
}
@@ -25,6 +25,7 @@ import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.linkage.Linked;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
@@ -44,14 +45,28 @@ public class Check implements Listener {
new StateDependentListener(ArenaMode.Check, FightState.All, this);
}
private boolean checkPermission(SteamwarUser user, SchematicNode schematic) {
GameModeConfig<Object, String> gameModeConfig = GameModeConfig.getAll().stream()
.filter(gmc -> gmc.Schematic.Type != null && gmc.Schematic.Type.checkType() != null && schematic.getSchemtype().name().equals(gmc.Schematic.Type.checkType().name()))
.findFirst()
.orElse(null);
if (gameModeConfig == null) gameModeConfig = GameModeConfig.getDefaults();
return checkPermission(user, gameModeConfig);
}
public static boolean checkPermission(SteamwarUser user, GameModeConfig gameModeConfig) {
if (user.hasPerm(UserPerm.ADMINISTRATION)) return true;
if (gameModeConfig.Checkers.isEmpty() && user.hasPerm(UserPerm.CHECK)) return true;
return gameModeConfig.Checkers.contains(user.getId());
}
@EventHandler
public void onJoin(PlayerJoinEvent e) {
Player player = e.getPlayer();
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
if (user.hasPerm(UserPerm.CHECK)) return;
SchematicNode schem = SchematicNode.getSchematicNode(Config.CheckSchemID);
if (checkPermission(user, schem)) return;
if (user.getId() == schem.getOwner()) return;
FightSystem.getMessage().send("CHECK_JOIN_DENIED", player);
@@ -72,6 +72,9 @@ public class WaterRemover implements Listener {
@EventHandler
public void handleEntityExplode(EntityExplodeEvent event) {
if (event.getEntityType() == EntityType.WIND_CHARGE && !Config.GameModeConfig.Arena.WindchargesDestroyWater) {
return;
}
event.setYield(0); //No drops (additionally to world config)
FightTeam spawn = tnt.remove(event.getEntity().getEntityId());
@@ -0,0 +1,44 @@
/*
* 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.listener;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.linkage.Linked;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityExplodeEvent;
@Linked
public class WindchargeInteractionDisabler implements Listener {
public WindchargeInteractionDisabler() {
new StateDependentListener(!Config.GameModeConfig.Arena.WindchargesInteractWithBlocks, FightState.Running, this);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void handleEntityExplode(EntityExplodeEvent event) {
if (event.getEntityType() != EntityType.WIND_CHARGE) return;
event.blockList().clear();
}
}
@@ -23,14 +23,14 @@ import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentTask;
import de.steamwar.linkage.Linked;
import net.minecraft.world.entity.projectile.windcharge.WindCharge;
import org.bukkit.Location;
import org.bukkit.entity.WindCharge;
@Linked
public class WindchargeStopper {
public WindchargeStopper() {
new StateDependentTask(true, FightState.Running, this::run, 1, 1);
new StateDependentTask(!Config.GameModeConfig.Arena.WindchargesCanCrossMiddle, FightState.Running, this::run, 1, 1);
}
private static final int middleLine = Config.SpecSpawn.getBlockZ();
@@ -39,13 +39,13 @@ public class WindchargeStopper {
private void run() {
Recording.iterateOverEntities(windChargeClass::isInstance, entity -> {
Location nextlocation = entity.getLocation().add(entity.getVelocity());
Location location = entity.getLocation();
Location prevLocation = location.clone().subtract(entity.getVelocity());
boolean passedMiddle = location.getBlockZ() > middleLine && prevLocation.getBlockZ() > middleLine ||
location.getBlockZ() < middleLine && prevLocation.getBlockZ() < middleLine;
boolean passedMiddle = nextlocation.getBlockZ() >= middleLine && location.getBlockZ() <= middleLine ||
nextlocation.getBlockZ() <= middleLine && location.getBlockZ() >= middleLine;
if (!passedMiddle) {
if (passedMiddle) {
entity.remove();
}
});
@@ -20,7 +20,7 @@
package de.steamwar.fightsystem.record;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import de.steamwar.core.WorldEditWrapper;
import de.steamwar.core.worldedit.WorldEditWrapper;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RPlayer;
@@ -19,6 +19,7 @@
package de.steamwar.fightsystem.states;
import de.steamwar.fightsystem.commands.FightSWCommand;
import lombok.Getter;
import java.util.*;
@@ -62,6 +63,7 @@ public enum FightState {
}
public static void setFightState(FightState state) {
if (fightState.equals(state)) return;
fightState = state;
for (Map.Entry<IStateDependent, Boolean> feature : stateDependentFeatures.entrySet()) {
@@ -77,6 +79,7 @@ public enum FightState {
feature.setValue(false);
}
}
FightSWCommand.postFightStateChange();
}
public static boolean setup() {
@@ -1,58 +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.fightsystem.states;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.FightSystem;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import java.util.Set;
public class StateDependentCommand extends StateDependent {
private static final CommandExecutor unavailable = (sender, cmd, s, strings) -> {
FightSystem.getMessage().sendPrefixless("COMMAND_CURRENTLY_UNAVAILABLE", sender, ChatMessageType.ACTION_BAR);
return false;
};
private final PluginCommand command;
private final CommandExecutor executor;
public StateDependentCommand(Set<ArenaMode> mode, Set<FightState> states, String name, CommandExecutor executor) {
super(mode, states);
this.executor = executor;
this.command = FightSystem.getPlugin().getCommand(name);
assert command != null;
disable();
register();
}
@Override
public void enable() {
command.setExecutor(executor);
}
@Override
public void disable() {
command.setExecutor(unavailable);
}
}
+1 -19
View File
@@ -10,22 +10,4 @@ depend:
- WorldEdit
api-version: "1.13"
commands:
ak:
request:
requests:
fightinfo:
leave:
ready:
kit:
remove:
lockschem:
state:
skip:
win:
resetwg:
resettb:
tpslimit:
tpswarp:
techhiderbug:
unrank:
commands:
+12
View File
@@ -53,3 +53,15 @@ tasks.register<FightServer>("WarGear21") {
config = "WarGear21.yml"
jar = "/jars/paper-1.21.6.jar"
}
tasks.register<FightServer>("WarShip21") {
group = "run"
description = "Run a WarShip 1.21 Fight Server"
dependsOn(":SpigotCore:shadowJar")
dependsOn(":FightSystem:shadowJar")
dependsOn(":KotlinCore:shadowJar")
template = "WarShip21"
worldName = "arenas/Artic"
config = "WarShip21.yml"
jar = "/jars/paper-1.21.6.jar"
}
+8
View File
@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
/*
* This file is a part of the SteamWar software.
*
@@ -34,6 +36,7 @@ dependencies {
compileOnly(libs.paperapi)
compileOnly(project(":SpigotCore"))
implementation(libs.coroutinesCore)
implementation(libs.exposedCore)
implementation(libs.exposedDao)
implementation(libs.exposedJdbc)
@@ -41,3 +44,8 @@ dependencies {
implementation(libs.mysql)
implementation("org.slf4j:slf4j-simple:2.0.17")
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.compilerOptions {
freeCompilerArgs.set(listOf("-XXLanguage:+ContextParameters"))
}
@@ -0,0 +1,23 @@
/*
* 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.kotlin.ui
@DslMarker
annotation class RenderMarker()
@@ -0,0 +1,26 @@
/*
* 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.kotlin.ui
interface RenderObject {
fun destroy()
fun render()
}
@@ -0,0 +1,90 @@
/*
* 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.kotlin.ui
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KProperty
private val uiStateScope = CoroutineScope(SupervisorJob() + Dispatchers.Unconfined)
context(render: StateFlowListener)
fun <T> StateFlow<T>.listen(): StateFlow<T> {
render.track(this) {
drop(1).onEach { render.update() }.launchIn(uiStateScope)
}
return this
}
context(render: StateFlowListener)
fun <T> MutableStateFlow<T>.listen(): MutableStateFlow<T> {
render.track(this) {
drop(1).onEach { render.update() }.launchIn(uiStateScope)
}
return this
}
fun <T> StateFlow<T>.listen(callback: (T) -> Unit): () -> Unit {
callback(value)
val job = drop(1).onEach { callback(it) }.launchIn(uiStateScope)
return { job.cancel() }
}
operator fun <T> StateFlow<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
operator fun <T> MutableStateFlow<T>.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
fun <T, R> StateFlow<T>.map(mapper: (T) -> R): StateFlow<R> = MappedStateFlow(this, mapper)
@OptIn(ExperimentalForInheritanceCoroutinesApi::class)
private class MappedStateFlow<T, R>(
private val parent: StateFlow<T>,
private val mapper: (T) -> R,
): StateFlow<R> {
override val replayCache: List<R>
get() = listOf(value)
override val value: R
get() = mapper(parent.value)
override suspend fun collect(collector: FlowCollector<R>): Nothing {
var initialized = false
var previous: Any? = null
parent.collect {
val mapped = mapper(it)
if (!initialized || previous != mapped) {
initialized = true
previous = mapped
collector.emit(mapped)
}
}
}
}
@@ -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.kotlin.ui
import kotlinx.coroutines.Job
abstract class StateFlowListener {
private val jobs = mutableMapOf<Any, Job>()
internal fun track(key: Any, createJob: () -> Job) {
if (key in jobs) return
val job = createJob()
jobs[key] = job
job.invokeOnCompletion { jobs.remove(key) }
}
abstract fun update()
open fun destroy() {
jobs.values.toList().forEach { it.cancel() }
jobs.clear()
}
}
@@ -0,0 +1,46 @@
/*
* 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.kotlin.ui
import de.steamwar.kotlin.ui.components.item
import de.steamwar.kotlin.util.count
import kotlinx.coroutines.flow.MutableStateFlow
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
val Counter = MutableStateFlow(0)
class TestInv(player: Player): UIInventory(player) {
override fun view() {
inventory(3, "") {
item {
val amount by Counter.map { it + 1 }.listen()
item = ItemStack.of(Material.STONE)
.count(amount)
x = 0
y = 0
onClick {
Counter.value += 1
}
}
}
}
}
@@ -0,0 +1,55 @@
/*
* 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.kotlin.ui
import de.steamwar.kotlin.ui.context.WindowContext
import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryType
abstract class UIInventory(val player: Player): StateFlowListener() {
var window: UIWindow? = null
abstract fun view()
fun open() {
if (window == null) {
view()
assert(window != null) { "View method must create a inventory" }
}
window!!.open()
}
fun render() {
window?.onClose()
window = null
open()
}
override fun update() = render()
protected fun inventory(size: Int, title: String, init: WindowContext.() -> Unit) {
window = UIWindow(size, title, player, init)
}
protected fun inventory(type: InventoryType, title: String, init: WindowContext.() -> Unit) {
window = UIWindow(type, title, player, init)
}
}
@@ -0,0 +1,91 @@
/*
* 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.kotlin.ui
import de.steamwar.kotlin.KotlinCore
import de.steamwar.kotlin.ui.context.WindowContext
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.inventory.ClickType
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.event.inventory.InventoryType
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.InventoryHolder
import org.bukkit.inventory.InventoryView
class UIWindow(val player: Player, val render: WindowContext.() -> Unit): InventoryHolder {
val onClicks = mutableMapOf<Int, (event: InventoryClickEvent) -> Unit>()
private var updating = false
lateinit var bukkitInv: Inventory
private set
val context by lazy { WindowContext(this) }
constructor(size: Int, title: String, player: Player, render: WindowContext.() -> Unit): this(player, render) {
bukkitInv = KotlinCore.plugin.server.createInventory(this, size * 9, Component.translatable(title))
}
constructor(type: InventoryType, title: String, player: Player, render: WindowContext.() -> Unit): this(player, render) {
assert(type != InventoryType.CHEST) { "Chest inventories should use the constructor with size" }
bukkitInv = KotlinCore.plugin.server.createInventory(this, type, Component.translatable(title))
}
fun open() {
render(context)
player.openInventory(bukkitInv)
}
fun onClose() {
if (updating) return
onClicks.clear()
context.destroy()
}
override fun getInventory() = bukkitInv
companion object : Listener {
init {
Bukkit.getPluginManager().registerEvents(this, KotlinCore.plugin)
}
@EventHandler
fun onInventoryClick(event: InventoryClickEvent) {
val window = event.inventory.holder
if (window is UIWindow) {
event.isCancelled = true
if (window.context.skipDoubleClick && event.click == ClickType.DOUBLE_CLICK) return
window.onClicks[event.slot]?.invoke(event)
}
}
@EventHandler
fun onInventoryClose(event: InventoryCloseEvent) {
val window = event.inventory.holder
if (window is UIWindow) {
window.onClose()
}
}
}
}
@@ -0,0 +1,66 @@
/*
* 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.kotlin.ui.components
import de.steamwar.kotlin.ui.StateFlowListener
import de.steamwar.kotlin.ui.RenderMarker
import de.steamwar.kotlin.ui.RenderObject
import de.steamwar.kotlin.ui.context.GroupContext
import de.steamwar.kotlin.ui.context.RenderParent
import org.bukkit.Material
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.inventory.ItemStack
@RenderMarker
class ItemContext(val parent: RenderParent, val renderFunc: ItemContext.() -> Unit): RenderObject, StateFlowListener() {
override fun update() {
val oldX = x
val oldY = y
render()
if (oldX != x || oldY != y) {
parent.resetSlot(oldX, oldY)
}
}
override fun destroy() {
super.destroy()
parent.resetSlot(x, y)
}
override fun render() {
renderFunc(this)
parent.renderItem(x, y, item, onClick)
}
init {
render()
}
var item: ItemStack = ItemStack.of(Material.AIR)
var onClick: (event: InventoryClickEvent) -> Unit = {}
var x: Int = 0
var y: Int = 0
fun onClick(func: (event: InventoryClickEvent) -> Unit) {
onClick = func
}
}
fun GroupContext.item(init: ItemContext.() -> Unit) = children.add(ItemContext(this, init))
@@ -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.kotlin.ui.context
import de.steamwar.kotlin.ui.StateFlowListener
import de.steamwar.kotlin.ui.RenderMarker
import de.steamwar.kotlin.ui.RenderObject
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.inventory.ItemStack
@RenderMarker
open class GroupContext(val parent: RenderParent?, val init: GroupContext.() -> Unit): StateFlowListener(), RenderObject, RenderParent {
val children = mutableListOf<RenderObject>()
val updatedSlots = mutableSetOf<Pair<Int, Int>>()
override fun update() = render()
override fun destroy() {
children.forEach { it.destroy() }
super.destroy()
}
init {
init(this)
}
override fun renderItem(x: Int, y: Int, item: ItemStack, onClick: (event: InventoryClickEvent) -> Unit) {
updatedSlots.add(x to y)
parent?.renderItem(x, y, item, onClick)
}
override fun resetSlot(x: Int, y: Int) {
parent?.resetSlot(x, y)
}
fun group(init: GroupContext.() -> Unit) {
children.add(GroupContext(this, init))
}
override fun render() {
children.forEach { it.destroy() }
children.clear()
val oldUpdatedSlots = updatedSlots.toList()
updatedSlots.clear()
init(this)
(oldUpdatedSlots - updatedSlots).forEach { resetSlot(it.first, it.second) }
}
}
@@ -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.kotlin.ui.context
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.inventory.ItemStack
interface RenderParent {
fun renderItem(x: Int, y: Int, item: ItemStack, onClick: (event: InventoryClickEvent) -> Unit)
fun resetSlot(x: Int, y: Int)
}
@@ -0,0 +1,54 @@
/*
* 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.kotlin.ui.context
import de.steamwar.kotlin.ui.RenderMarker
import de.steamwar.kotlin.ui.UIWindow
import org.bukkit.Material
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryType
import org.bukkit.inventory.ItemStack
@RenderMarker
class WindowContext(val window: UIWindow): GroupContext(null, {}) {
override fun renderItem(x: Int, y: Int, item: ItemStack, onClick: (event: InventoryClickEvent) -> Unit) {
val slot = calculateSlot(x, y)
window.bukkitInv.setItem(slot, item)
window.onClicks[slot] = onClick
}
override fun resetSlot(x: Int, y: Int) {
val slot = calculateSlot(x, y)
window.bukkitInv.setItem(slot, ItemStack.of(Material.AIR))
window.onClicks.remove(slot)
}
private fun calculateSlot(x: Int, y: Int) = when (window.inventory.type) {
InventoryType.DROPPER, InventoryType.DISPENSER -> x + y * 3
InventoryType.HOPPER -> x
else -> x + y * 9
}
fun outsideClick(click: (event: InventoryClickEvent) -> Unit) {
window.onClicks[-999] = click
}
var skipDoubleClick = true
}
@@ -0,0 +1,50 @@
/*
* 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.kotlin.util
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.enchantments.Enchantment
import org.bukkit.inventory.ItemFlag
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.SkullMeta
import org.bukkit.inventory.meta.components.CustomModelDataComponent
fun ItemStack.count(count: Int) = apply { amount = count }
fun ItemStack.name(name: Component) = apply { itemMeta = itemMeta.also { it.displayName(name) } }
fun ItemStack.lored(lore: List<Component>) = apply { itemMeta = itemMeta.also { it.lore(lore) } }
fun String.asMaterial(): Material = Material.matchMaterial(this) ?: Material.BARRIER
fun skull(owner: String): ItemStack = ItemStack(Material.PLAYER_HEAD)
.also {
it.editMeta(SkullMeta::class.java) {
it.playerProfile = Bukkit.getOfflinePlayer(owner.trimStart('.')).playerProfile.also { pp -> pp.complete() }
}
}
fun ItemStack.hideAttributes() = apply { itemMeta = itemMeta.also { it.addItemFlags(*ItemFlag.entries.toTypedArray()) } }
fun ItemStack.enchanted() = apply { itemMeta = itemMeta.also { it.addEnchant(Enchantment.UNBREAKING, 10, true) } }
fun ItemStack.customModelData(model: CustomModelDataComponent) = apply { itemMeta = itemMeta.also { it.setCustomModelDataComponent(model) } }
+1
View File
@@ -42,6 +42,7 @@ tasks.register<DevServer>("DevLobby") {
group = "run"
description = "Run a Dev Lobby"
dependsOn(":SpigotCore:shadowJar")
dependsOn(":KotlinCore:shadowJar")
dependsOn(":LobbySystem:jar")
template = "Lobby21"
worldName = "Lobby"
@@ -20,7 +20,6 @@
package de.steamwar.lobby.boatrace;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityAction;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RInteraction;
import de.steamwar.lobby.LobbySystem;
@@ -36,6 +35,7 @@ import org.bukkit.entity.Boat;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.boat.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
@@ -44,6 +44,7 @@ import org.bukkit.event.vehicle.VehicleMoveEvent;
import org.bukkit.scheduler.BukkitTask;
import java.util.EventListener;
import java.util.Random;
import static de.steamwar.lobby.util.LeaderboardManager.renderTime;
@@ -61,11 +62,11 @@ public class BoatRace implements EventListener, Listener {
static {
boatNpcServer = new REntityServer();
new REntity(boatNpcServer, EntityType.VILLAGER, BoatRacePositions.NPC);
RInteraction interaction = new RInteraction(boatNpcServer, BoatRacePositions.NPC.clone().subtract(0.5, 0, 0.5));
RInteraction interaction = new RInteraction(boatNpcServer, BoatRacePositions.NPC.clone());
interaction.setInteractionHeight(1.95f);
interaction.setCallback((player, entity, action) -> {
Bukkit.getWorlds().get(0).getEntities().stream().filter(e -> e.getType() == EntityType.END_CRYSTAL).forEach(Entity::remove);
if (action == REntityAction.INTERACT && !oneNotStarted) {
if (!oneNotStarted) {
oneNotStarted = true;
new BoatRace(player);
}
@@ -153,10 +154,24 @@ public class BoatRace implements EventListener, Listener {
oneNotStarted = false;
}
private final Class<? extends Boat>[] boatClasses = new Class[]{
AcaciaBoat.class,
BambooRaft.class,
BirchBoat.class,
CherryBoat.class,
DarkOakBoat.class,
JungleBoat.class,
MangroveBoat.class,
OakBoat.class,
PaleOakBoat.class,
SpruceBoat.class,
};
private final Random random = new Random();
public BoatRace(Player player) {
this.player = player;
boat = Bukkit.getWorlds().get(0).spawn(BoatRacePositions.START, Boat.class);
// boat.setBoatType(Boat.Type.values()[new Random().nextInt(Boat.Type.values().length)]);
boat = Bukkit.getWorlds().get(0).spawn(BoatRacePositions.START, boatClasses[random.nextInt(boatClasses.length)]);
boat.addPassenger(player);
bossBar = Bukkit.createBossBar("", BarColor.BLUE, BarStyle.SOLID);
task = Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> {
@@ -42,7 +42,10 @@ import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.Month;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
public class CustomMap implements Listener {
@@ -56,7 +59,7 @@ public class CustomMap implements Listener {
new Vector(2346, 45, 1297), new Vector(2345, 45, 1297), new Vector(2344, 45, 1297), new Vector(2343, 45, 1297), new Vector(2342, 45, 1297), new Vector(2341, 45, 1297), new Vector(2340, 45, 1297)
);
private static final CustomMap RIGHT = new CustomMap(new File(System.getProperty("user.home") + "/lobbyBanner/right.png"),
private static final CustomMap RIGHT = new CustomMap(new File(System.getProperty("user.home") + "/lobbyBanner/right/"),
new Vector(2330, 48, 1297), new Vector(2329, 48, 1297), new Vector(2328, 48, 1297), new Vector(2327, 48, 1297), new Vector(2326, 48, 1297), new Vector(2325, 48, 1297), new Vector(2324, 48, 1297),
new Vector(2330, 47, 1297), new Vector(2329, 47, 1297), new Vector(2328, 47, 1297), new Vector(2327, 47, 1297), new Vector(2326, 47, 1297), new Vector(2325, 47, 1297), new Vector(2324, 47, 1297),
new Vector(2330, 46, 1297), new Vector(2329, 46, 1297), new Vector(2328, 46, 1297), new Vector(2327, 46, 1297), new Vector(2326, 46, 1297), new Vector(2325, 46, 1297), new Vector(2324, 46, 1297),
@@ -66,32 +69,54 @@ public class CustomMap implements Listener {
private File mapFile;
private Map<Vector, Integer> itemFrameIndex = new HashMap<>();
private ItemFrame[] itemFrames;
private long lastModified = Long.MAX_VALUE;
private boolean update = true;
public CustomMap(File mapFile, Vector... itemFrames) {
this.mapFile = mapFile;
public CustomMap(File mapFileOrDirectory, Vector... itemFrames) {
this.mapFile = mapFileOrDirectory;
this.itemFrames = new ItemFrame[itemFrames.length];
for (int i = 0; i < itemFrames.length; i++) {
itemFrameIndex.put(itemFrames[i], i);
}
Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> {
long modified = mapFile.lastModified();
if (modified > lastModified) {
lastModified = modified;
System.out.println("Updating Banner: " + mapFile.getName());
Bukkit.getScheduler().runTaskAsynchronously(LobbySystem.getInstance(), () -> {
try {
run();
} catch (IOException e) {
// Ignore
}
});
}
}, 200L, 200L);
if (mapFileOrDirectory.isDirectory()) {
AtomicReference<Month> lastMonth = new AtomicReference<>(LocalDateTime.now().getMonth());
AtomicReference<Long> lastModified = new AtomicReference<>(Long.MAX_VALUE);
Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> {
long modified = mapFileOrDirectory.lastModified();
Month current = LocalDateTime.now().getMonth();
if (modified > lastModified.get() || !current.equals(lastMonth.get()) || update) {
lastModified.set(modified);
lastMonth.set(current);
update = false;
this.mapFile = new File(mapFileOrDirectory, current.getValue() + ".png");
update();
}
}, 200L, 1200L);
} else {
AtomicReference<Long> lastModified = new AtomicReference<>(Long.MAX_VALUE);
Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> {
long modified = mapFileOrDirectory.lastModified();
if (modified > lastModified.get() || update) {
lastModified.set(modified);
update = false;
update();
}
}, 200L, 200L);
}
Bukkit.getPluginManager().registerEvents(this, LobbySystem.getInstance());
}
private void update() {
System.out.println("Updating Banner: " + mapFile.getName());
Bukkit.getScheduler().runTaskAsynchronously(LobbySystem.getInstance(), () -> {
try {
run();
} catch (IOException e) {
// Ignore
}
});
}
@EventHandler
public void onChunkLoad(ChunkLoadEvent event) {
for (Entity entity : event.getChunk().getEntities()) {
@@ -101,7 +126,7 @@ public class CustomMap implements Listener {
if (itemFrameIndex.containsKey(vector)) {
if (itemFrames[itemFrameIndex.get(vector)] != null) continue;
itemFrames[itemFrameIndex.get(vector)] = itemFrame;
lastModified = 0;
update = true;
ItemStack itemStack = new ItemStack(Material.FILLED_MAP, 1);
MapMeta mapMeta = (MapMeta) itemStack.getItemMeta();
+11 -1
View File
@@ -29,5 +29,15 @@ dependencies {
compileOnly(libs.paperapi)
compileOnly(libs.nms)
compileOnly(libs.worldedit)
compileOnly(libs.fawe)
}
tasks.register<FightServer>("MissileWars21") {
group = "run"
description = "Run a 1.21 Dev MissileWars"
dependsOn(":SpigotCore:shadowJar")
dependsOn(":MissileWars:jar")
template = "MissileWars"
worldName = "Great_Wall"
jar = "/jars/paper-1.21.6.jar"
}
@@ -69,6 +69,7 @@ public class CustomItem extends SpecialItem {
}
for (File itemFile : Objects.requireNonNull(itemsFolder.listFiles())) {
if (!itemFile.canRead() || !itemFile.isFile()) continue;
if (!itemFile.getName().endsWith(".json")) continue;
try {
JsonObject jsonObject = new JsonParser().parse(new FileReader(itemFile)).getAsJsonObject();
new CustomItem(new ScriptedItem(jsonObject));
@@ -30,6 +30,8 @@ import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.block.BlockTypes;
import de.steamwar.misslewars.MissileWars;
@@ -109,11 +111,17 @@ public class Missile extends SpecialItem {
v = aT.apply(v.toVector3()).toBlockPoint();
v = v.add(location.getBlockX(), location.getBlockY(), location.getBlockZ());
EditSession e = WorldEdit.getInstance().getEditSessionFactory().getEditSession(world, -1);
EditSession e = WorldEdit.getInstance().getEditSessionFactory()
.getEditSession(world, -1);
e.setSideEffectApplier(SideEffectSet.defaults()
.with(SideEffect.NEIGHBORS, SideEffect.State.ON)
.with(SideEffect.LIGHTING, SideEffect.State.ON)
.with(SideEffect.UPDATE, SideEffect.State.ON));
ClipboardHolder ch = new ClipboardHolder(clipboard);
ch.setTransform(aT);
Operations.completeBlindly(ch.createPaste(e).to(v).ignoreAirBlocks(true).build());
e.flushSession();
return true;
}
@@ -25,6 +25,7 @@ import de.steamwar.misslewars.scripts.RunnableScriptEvent;
import de.steamwar.misslewars.scripts.ScriptedItem;
import de.steamwar.misslewars.scripts.utils.EntityUtils;
import de.steamwar.misslewars.scripts.utils.EntityUtils.ScriptShortcut;
import org.bukkit.Material;
import org.bukkit.entity.Projectile;
public class LaunchScript implements RunnableScript {
@@ -75,6 +75,11 @@ public class EntityUtils {
});
case "arrow":
return new ScriptShortcut<>(Arrow.class, (jsonObject, entity, runnableScriptEvent) -> setProjectileOptions(entity, jsonObject));
case "windcharge":
return new ScriptShortcut<>(WindCharge.class, (jsonObject, entity, runnableScriptEvent) -> {
setFireballOptions(entity, jsonObject);
entity.setDirection(runnableScriptEvent.getLocation().getDirection());
});
}
return null;
}
@@ -269,7 +269,7 @@ AUTO_CHECKER_RESULT_DEFUNCT_NBT = §7Defunct NBT: §7[{0}, {1}, {2}]
AUTO_CHECKER_RESULT_DESIGN_BLOCK = §7{0} in Design: [{1}, {2}, {3}]
AUTO_CHECKER_RESULT_ENTITY = §7Entity: §7[{0}, {1}, {2}]
AUTO_CHECKER_RESULT_RECORD = §7Record: §c[{0}, {1}, {2}]
AUTO_CHECKER_RESULT_TOO_MANY_DISPENSER_ITEMS = §7Dispenser: §c[{0}, {1}, {2}]§7, §c{3} §7items, Max: §e{4}
AUTO_CHECKER_RESULT_TOO_MANY_INVENTORY_ITEMS = §7{6}: §c[{0}, {1}, {2}]§7, §c{3}x {5}§7, Max: §e{4}
AUTO_CHECKER_RESULT_FORBIDDEN_ITEM_NBT = §7Forbidden Item NBT: [{0}, {1}, {2}] -> §c{3}
AUTO_CHECKER_RESULT_TELEPORT_HERE = §7Teleport to block
AUTO_CHECKER_RESULT_AFTER_DEADLINE = §cThe deadline has expired: {0}
@@ -248,7 +248,7 @@ AUTO_CHECKER_RESULT_FORBIDDEN_ITEM = §7Verbotener gegenstand: [{0}, {1}, {2}] -
AUTO_CHECKER_RESULT_DEFUNCT_NBT = §7Keine NBT-Daten: §c[{0}, {1}, {2}]
AUTO_CHECKER_RESULT_DESIGN_BLOCK = §7{0} im Design: [{1}, {2}, {3}]
AUTO_CHECKER_RESULT_RECORD = §7Schallplatte: §c[{0}, {1}, {2}]
AUTO_CHECKER_RESULT_TOO_MANY_DISPENSER_ITEMS = §7Dispenser: §c[{0}, {1}, {2}]§7, §c{3} §7gegenstände, Max: §e{4}
AUTO_CHECKER_RESULT_TOO_MANY_INVENTORY_ITEMS = §7{6}: §c[{0}, {1}, {2}]§7, §c{3}x {5}§7, Max: §e{4}
AUTO_CHECKER_RESULT_FORBIDDEN_ITEM_NBT = §7Verbotene NBT-Daten: [{0}, {1}, {2}] -> §c{3}
AUTO_CHECKER_RESULT_TELEPORT_HERE = §7Zum block teleportieren
AUTO_CHECKER_RESULT_AFTER_DEADLINE = §cDer einsendeschluss ist bereits vorbei: {0}
@@ -37,17 +37,27 @@ public class AutoChecker {
public static final AutoChecker impl = new AutoChecker();
public AutoCheckerResult check(Clipboard clipboard, GameModeConfig<Material, String> type) {
return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().x()).width(clipboard.getDimensions().x())
.depth(clipboard.getDimensions().z()).blockScanResult(scan(clipboard, type))
.entities(clipboard.getEntities().stream().map(Entity::getLocation)
.map(blockVector3 -> new BlockPos(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ()))
.collect(Collectors.toList()))
return AutoCheckerResult.builder()
.type(type)
.height(clipboard.getDimensions().y())
.width(clipboard.getDimensions().x())
.depth(clipboard.getDimensions().z())
.blockScanResult(scan(clipboard, type))
.entities(
clipboard.getEntities().stream()
.map(Entity::getLocation)
.map(blockVector3 -> new BlockPos(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ()))
.collect(Collectors.toList()))
.build();
}
public AutoCheckerResult sizeCheck(Clipboard clipboard, GameModeConfig<Material, String> type) {
return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().y()).width(clipboard.getDimensions().x())
.depth(clipboard.getDimensions().z()).build();
return AutoCheckerResult.builder()
.type(type)
.height(clipboard.getDimensions().y())
.width(clipboard.getDimensions().x())
.depth(clipboard.getDimensions().z())
.build();
}
public AutoChecker.BlockScanResult scan(Clipboard clipboard, GameModeConfig<Material, String> type) {
@@ -63,14 +73,19 @@ public class AutoChecker {
continue;
}
result.getBlockCounts().merge(material, 1, Integer::sum);
BlockPos pos = new BlockPos(x, y, z);
if (AutoCheckerItems.impl.getInventoryMaterials().contains(material)) {
checkInventory(result, block, material, new BlockPos(x, y, z), type);
if (type.Schematic.isInventory(material)) {
checkInventory(result, block, material, pos, type);
if (!result.getInventoryItemCounts().getOrDefault(pos, Collections.emptyMap()).isEmpty()) {
result.getBlockCounts().merge(material, 1, Integer::sum);
}
} else {
result.getBlockCounts().merge(material, 1, Integer::sum);
}
if (x == min.x() || x == max.x() || y == max.y() || z == min.z() || z == max.z()) {
result.getDesignBlocks().computeIfAbsent(material, m -> new ArrayList<>()).add(new BlockPos(x, y, z));
result.getDesignBlocks().computeIfAbsent(material, m -> new ArrayList<>()).add(pos);
}
}
}
@@ -78,19 +93,6 @@ public class AutoChecker {
return result;
}
private static final Map<Material, Set<Material>> itemsInInv = new EnumMap<>(Material.class);
static {
itemsInInv.put(Material.BUCKET, EnumSet.of(Material.DISPENSER));
itemsInInv.put(Material.TNT, EnumSet.of(Material.CHEST, Material.BARREL, Material.SHULKER_BOX, Material.BLACK_SHULKER_BOX, Material.BLUE_SHULKER_BOX,
Material.BROWN_SHULKER_BOX, Material.CYAN_SHULKER_BOX, Material.GRAY_SHULKER_BOX, Material.GREEN_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX,
Material.LIGHT_GRAY_SHULKER_BOX, Material.LIME_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.ORANGE_SHULKER_BOX,
Material.PINK_SHULKER_BOX, Material.PURPLE_SHULKER_BOX, Material.RED_SHULKER_BOX, Material.WHITE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX));
itemsInInv.put(Material.FIRE_CHARGE, EnumSet.of(Material.DISPENSER));
itemsInInv.put(Material.ARROW, EnumSet.of(Material.DISPENSER));
AutoCheckerItems.impl.getAllowedMaterialsInInventory().forEach(material -> itemsInInv.put(material, AutoCheckerItems.impl.getInventoryMaterials()));
}
private void checkInventory(AutoChecker.BlockScanResult result, BaseBlock block, Material material, BlockPos pos, GameModeConfig<Material, String> type) {
CompoundTag nbt = block.getNbtData();
if (nbt == null) {
@@ -107,7 +109,7 @@ public class AutoChecker {
List<CompoundTag> items = nbt.getList("Items", CompoundTag.class);
if (items.isEmpty()) return; // Leeres Inventar
int counter = 0;
Map<Material, Integer> itemCounts = new EnumMap<>(Material.class);
int windChargeCount = 0;
for (CompoundTag item : items) {
if (!item.containsKey("id")) {
@@ -121,16 +123,19 @@ public class AutoChecker {
if (type.Schematic.Type.getName().equals("wargearseason26") && material == Material.DISPENSER && itemType == Material.WIND_CHARGE) {
windChargeCount += item.getInt("count");
} else if (!itemsInInv.getOrDefault(itemType, EnumSet.noneOf(Material.class)).contains(material)) {
} else if (type.Schematic.getItemAmount(material, itemType) == 0) {
result.getForbiddenItems().computeIfAbsent(pos, blockVector3 -> new HashSet<>()).add(itemType);
} else if (material == Material.DISPENSER && (itemType == Material.ARROW || itemType == Material.FIRE_CHARGE)) {
counter += item.getInt("count");
} else {
itemCounts.merge(itemType, item.getInt("count"), Integer::sum);
}
if (item.containsKey("tag")) {
result.getForbiddenNbt().computeIfAbsent(pos, blockVector3 -> new HashSet<>()).add(itemType);
}
}
result.getDispenserItems().put(pos, counter);
if (!itemCounts.isEmpty()) {
result.getInventoryItemCounts().put(pos, itemCounts);
result.getInventoryBlockType().put(pos, material);
}
result.getWindChargeCount().put(pos, windChargeCount);
}
@@ -141,7 +146,8 @@ public class AutoChecker {
private final List<BlockPos> defunctNbt = new ArrayList<>();
private final List<BlockPos> records = new ArrayList<>();
private final Map<Material, List<BlockPos>> designBlocks = new EnumMap<>(Material.class);
private final Map<BlockPos, Integer> dispenserItems = new HashMap<>();
private final Map<BlockPos, Map<Material, Integer>> inventoryItemCounts = new HashMap<>();
private final Map<BlockPos, Material> inventoryBlockType = new HashMap<>();
private final Map<BlockPos, Integer> windChargeCount = new HashMap<>();
private final Map<BlockPos, Set<Material>> forbiddenItems = new HashMap<>();
private final Map<BlockPos, Set<Material>> forbiddenNbt = new HashMap<>();
@@ -1,51 +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.schematicsystem.autocheck;
import org.bukkit.Material;
import java.util.EnumSet;
import java.util.Set;
public class AutoCheckerItems {
public static final AutoCheckerItems impl = new AutoCheckerItems();
private static final Set<Material> INVENTORY = EnumSet.of(Material.BARREL, Material.BLAST_FURNACE, Material.BREWING_STAND, Material.CAMPFIRE,
Material.CHEST, Material.DISPENSER, Material.DROPPER, Material.FURNACE, Material.HOPPER, Material.JUKEBOX, Material.SHULKER_BOX,
Material.WHITE_SHULKER_BOX, Material.ORANGE_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX,
Material.LIME_SHULKER_BOX, Material.PINK_SHULKER_BOX, Material.GRAY_SHULKER_BOX, Material.LIGHT_GRAY_SHULKER_BOX, Material.CYAN_SHULKER_BOX,
Material.PURPLE_SHULKER_BOX, Material.BLUE_SHULKER_BOX, Material.BROWN_SHULKER_BOX, Material.GREEN_SHULKER_BOX, Material.RED_SHULKER_BOX,
Material.BLACK_SHULKER_BOX, Material.SMOKER, Material.TRAPPED_CHEST);
private static final Set<Material> FLOWERS = EnumSet.of(Material.CORNFLOWER, Material.POPPY, Material.FERN, Material.DANDELION, Material.BLUE_ORCHID,
Material.ALLIUM, Material.AZURE_BLUET, Material.RED_TULIP, Material.ORANGE_TULIP, Material.WHITE_TULIP, Material.PINK_TULIP, Material.OXEYE_DAISY,
Material.LILY_OF_THE_VALLEY, Material.WITHER_ROSE, Material.SUNFLOWER, Material.DIAMOND_HORSE_ARMOR, Material.IRON_HORSE_ARMOR,
Material.GOLDEN_HORSE_ARMOR, Material.LEATHER_HORSE_ARMOR, Material.HONEY_BOTTLE, Material.LILAC, Material.ROSE_BUSH, Material.PEONY,
Material.TALL_GRASS, Material.LARGE_FERN);
public Set<Material> getInventoryMaterials() {
return INVENTORY;
}
public Set<Material> getAllowedMaterialsInInventory() {
return FLOWERS;
}
}
@@ -50,7 +50,7 @@ public class AutoCheckerResult {
isSizeOk() &&
isBlockCountOk() &&
isLimitedBlocksOK() &&
isDispenserItemsOK() &&
isInventoryItemsOK() &&
isWindchargeCountOK() &&
!type.isAfterDeadline() &&
entities.isEmpty() &&
@@ -71,8 +71,14 @@ public class AutoCheckerResult {
}
}
public boolean isDispenserItemsOK() {
return blockScanResult.getDispenserItems().values().stream().allMatch(i -> i <= type.Schematic.MaxDispenserItems);
public boolean isInventoryItemsOK() {
return blockScanResult.getInventoryItemCounts().entrySet().stream().allMatch(posEntry -> {
Material inventory = blockScanResult.getInventoryBlockType().get(posEntry.getKey());
return posEntry.getValue().entrySet().stream().allMatch(itemEntry -> {
int cap = type.Schematic.getItemAmount(inventory, itemEntry.getKey());
return itemEntry.getValue() <= cap;
});
});
}
public boolean hasWarnings() {
@@ -100,13 +106,8 @@ public class AutoCheckerResult {
}
public boolean isLimitedBlocksOK() {
try {
return type.Schematic.Limited.entrySet().stream()
.map(setIntegerEntry -> setIntegerEntry.getKey().stream().map(blockScanResult.getBlockCounts()::get).map(i -> i == null || i <= setIntegerEntry.getValue()).reduce(Boolean::logicalAnd).orElse(false))
.reduce(Boolean::logicalAnd).orElse(true);
} catch (NullPointerException e) {
return false;
}
return blockScanResult.getBlockCounts().entrySet().stream()
.allMatch(entry -> entry.getValue() <= type.Schematic.getMaxCount(entry.getKey()));
}
public boolean isDesignBlastResistanceOK() {
@@ -130,14 +131,13 @@ public class AutoCheckerResult {
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_BLOCKS", p, blockScanResult.getBlockCounts().values().stream().reduce(Integer::sum).orElse(0), type.Schematic.MaxBlocks);
}
if (!isLimitedBlocksOK()) {
type.Schematic.Limited.forEach((materials, integer) -> {
for (Material mat : materials) {
if (mat != null && blockScanResult.getBlockCounts().getOrDefault(mat, 0) > integer) {
if (integer == 0) {
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_FORBIDDEN_BLOCK", p, mat.name());
} else {
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_TOO_MANY_BLOCK", p, mat.name(), blockScanResult.getBlockCounts().getOrDefault(mat, 0), integer);
}
blockScanResult.getBlockCounts().forEach((mat, count) -> {
int maxCount = type.Schematic.getMaxCount(mat);
if (count > maxCount) {
if (maxCount == 0) {
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_FORBIDDEN_BLOCK", p, mat.name());
} else {
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_TOO_MANY_BLOCK", p, mat.name(), count, maxCount);
}
}
});
@@ -155,13 +155,20 @@ public class AutoCheckerResult {
});
}
blockScanResult.getDispenserItems().entrySet().stream().filter(blockVector3IntegerEntry -> blockVector3IntegerEntry.getValue() > type.Schematic.MaxDispenserItems).forEach(blockVector3IntegerEntry -> {
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_TOO_MANY_DISPENSER_ITEMS", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3IntegerEntry.getKey()),
blockVector3IntegerEntry.getKey().getBlockX(),
blockVector3IntegerEntry.getKey().getBlockY(),
blockVector3IntegerEntry.getKey().getBlockZ(),
blockVector3IntegerEntry.getValue(),
type.Schematic.MaxDispenserItems);
blockScanResult.getInventoryItemCounts().forEach((pos, itemCounts) -> {
Material inventory = blockScanResult.getInventoryBlockType().get(pos);
itemCounts.forEach((itemType, count) -> {
int cap = type.Schematic.getItemAmount(inventory, itemType);
if (count <= cap) return;
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_TOO_MANY_INVENTORY_ITEMS", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(pos),
pos.getBlockX(),
pos.getBlockY(),
pos.getBlockZ(),
count,
cap,
itemType.name(),
inventory.name());
});
});
blockScanResult.getRecords().forEach(blockVector3 -> {
@@ -197,27 +197,31 @@ public class SchematicCommand extends SWCommand {
clipboard.setBlock(vector, block.toBaseBlock(builder.build()));
}
if (type.Schematic.MaxDispenserItems > 0) {
for (Map.Entry<BlockPos, Integer> entry : result.getBlockScanResult().getDispenserItems().entrySet()) {
if (entry.getValue() <= type.Schematic.MaxDispenserItems) {
for (Map.Entry<BlockPos, Map<Material, Integer>> posEntry : result.getBlockScanResult().getInventoryItemCounts().entrySet()) {
BlockPos pos = posEntry.getKey();
Material inventory = result.getBlockScanResult().getInventoryBlockType().get(pos);
for (Map.Entry<Material, Integer> itemEntry : posEntry.getValue().entrySet()) {
Material itemType = itemEntry.getKey();
Integer cap = type.Schematic.getItemAmount(inventory, itemType);
if (itemEntry.getValue() <= cap) {
continue;
}
BlockPos pos = entry.getKey();
BlockVector3 vector = BlockVector3.at(pos.getX(), pos.getY(), pos.getZ());
BaseBlock block = clipboard.getFullBlock(vector);
CompoundTag tag = block.getNbtData();
CompoundTagBuilder builder = tag.createBuilder();
List<CompoundTag> items = new ArrayList<>(tag.getList("Items", CompoundTag.class));
Collections.reverse(items); // To let the first item be in the Dispenser
Collections.reverse(items); // To let the first item be in the inventory
List<CompoundTag> list = new ArrayList<>();
int diff = entry.getValue() - type.Schematic.MaxDispenserItems;
int diff = itemEntry.getValue() - cap;
for (CompoundTag item : items) {
if (item == null) {
continue;
}
if (diff == 0) {
if (diff == 0 || Material.matchMaterial(item.getString("id")) != itemType) {
list.add(item);
continue;
}
@@ -238,9 +242,9 @@ public class SchematicCommand extends SWCommand {
}
if (!result.isLimitedBlocksOK()) {
Set<Material> toReplace = type.Schematic.Limited.entrySet().stream()
.filter(setIntegerEntry -> setIntegerEntry.getValue() == 0)
.flatMap(setIntegerEntry -> setIntegerEntry.getKey().stream())
Set<Material> toReplace = type.Schematic.Limited.stream()
.filter(rule -> rule.Amount == 0)
.flatMap(rule -> rule.Materials.stream())
.collect(Collectors.toSet());
BlockState replaceType = Objects.requireNonNull(toReplace.contains(Material.END_STONE) ? BlockTypes.IRON_BLOCK : BlockTypes.END_STONE).getDefaultState();
BlockVector3 min = clipboard.getMinimumPoint();
@@ -108,19 +108,33 @@ UNNOSCHEMSUBMITTING = §e{0} §7may now submit §e§lschematics§7 again§8.
WORLDEDIT_CUI_TITLE = WorldEdit CUI
WORLDEDIT_CUI_TITLE_SUBMENU = WorldEdit CUI - {0}
WORLDEDIT_CUI_SELECTION = Own Selection
WORLDEDIT_CUI_CLIPBOARD = Own Clipboard
WORLDEDIT_CUI_SELECTION_OTHER = Other Selection
WORLDEDIT_CUI_CLIPBOARD_OTHER = Other Clipboard
WORLDEDIT_CUI_SELECTION = Selection
WORLDEDIT_CUI_CLIPBOARD = Clipboard
WORLDEDIT_CUI_CURSOR = Cursor
WORLDEDIT_CUI_MATERIAL_NAME = §eWorldEdit {0}
WORLDEDIT_CUI_MATERIAL_CLICK = §7Click to edit
WORLDEDIT_CUI_WIDTH_NAME = §eWidth {0}
WORLDEDIT_CUI_WIDTH_NAME_DIST = §eRange {0}
WORLDEDIT_CUI_WIDTH_LORE = §8> §7{0}
WORLDEDIT_CUI_WIDTH_LORE_SELECTED = §8> §e{0}
WORLDEDIT_CUI_WIDTH_CLICK = §7Click to change
WORLDEDIT_CUI_WIDTH_HUGE = 1/ 8 Block
WORLDEDIT_CUI_WIDTH_LARGE = 1/16 Block
WORLDEDIT_CUI_WIDTH_MEDIUM = 1/32 Block
WORLDEDIT_CUI_WIDTH_SLIM = 1/64 Block
WORLDEDIT_CUI_WIDTH_SLIM = 1/64 Block
WORLDEDIT_CUI_WIDTH_HUGE_DIST = 40 Blocks
WORLDEDIT_CUI_WIDTH_LARGE_DIST = 30 Blocks
WORLDEDIT_CUI_WIDTH_MEDIUM_DIST = 20 Blocks
WORLDEDIT_CUI_WIDTH_SLIM_DIST = 10 Blocks
WORLDEDIT_CUI_TOOL_SOON = §7Tool cooming soon!
WORLDEDIT_CUI_TOOL_COPY = §eCopy Tool
WORLDEDIT_CUI_TOOL_COPY_LORE = §eClick§8:§7 copy selection
WORLDEDIT_CUI_TOOL_PASTE = §ePaste Tool
WORLDEDIT_CUI_TOOL_PASTE_LORE_1 = §eClick§8:§7 paste clipboard
WORLDEDIT_CUI_TOOL_PASTE_LORE_2 = §eShift §8+§e Click§8:§7 paste clipboard and select
WORLDEDIT_CUI_TOOL_PASTE_LORE_3 = §eDrop§8:§7 paste clipboard at copy origin
@@ -101,13 +101,20 @@ NOSCHEMSUBMITTING_UNTIL = §7Du bist §ebis zum {0} §7vom §e§lEinsenden von S
UNNOSCHEMSUBMITTING_ERROR = §cDer Spieler ist nicht vom Einsenden von Schematics ausgeschlossen.
UNNOSCHEMSUBMITTING = §e{0} §7darf nun wieder §e§lSchematis§7 einsenden§8.
WORLDEDIT_CUI_SELECTION = Eigene Auswahl
WORLDEDIT_CUI_CLIPBOARD = Eigene Kopie
WORLDEDIT_CUI_SELECTION_OTHER = Auswahl Anderer
WORLDEDIT_CUI_CLIPBOARD_OTHER = Kopie Anderer
WORLDEDIT_CUI_SELECTION = Auswahl
WORLDEDIT_CUI_CLIPBOARD = Kopie
WORLDEDIT_CUI_MATERIAL_NAME = §eWorldEdit {0}
WORLDEDIT_CUI_MATERIAL_CLICK = §7Klicke zum Editieren
WORLDEDIT_CUI_WIDTH_NAME = §eDicke - {0}
WORLDEDIT_CUI_WIDTH_CLICK = §7Klicke zum ändern
WORLDEDIT_CUI_WIDTH_NAME = §eKlicke - {0}
WORLDEDIT_CUI_WIDTH_NAME_DIST = Reichweite§e {0}
WORLDEDIT_CUI_WIDTH_CLICK = §7Klicke zum ändern
WORLDEDIT_CUI_TOOL_SOON = §7Werkzeug kommt in kürze!
WORLDEDIT_CUI_TOOL_COPY = §eCopy Werkzeug
WORLDEDIT_CUI_TOOL_COPY_LORE = §eKlick§8:§7 Auswahl zu kopieren
WORLDEDIT_CUI_TOOL_PASTE = §ePaste Werkzeug
WORLDEDIT_CUI_TOOL_PASTE_LORE_1 = §eKlick§8:§7 Clipboard einzufügen
WORLDEDIT_CUI_TOOL_PASTE_LORE_2 = §eUmschalt §8+§e Klick§8:§7 Clipboard einzufügen und auszuwählen
WORLDEDIT_CUI_TOOL_PASTE_LORE_3 = §eDrop§8:§7 Clipboard am Kopierpunkt einzufügen
@@ -21,8 +21,8 @@ package de.steamwar.core;
import de.steamwar.message.Message;
import lombok.Data;
import lombok.Getter;
import lombok.NonNull;
import lombok.experimental.Delegate;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.ClickEvent;
import org.bukkit.Bukkit;
@@ -99,11 +99,11 @@ public class SWPlayer {
@Data
public static final class SWPlayerWithComponent<T extends Component> {
private final SWPlayer player;
private final SWPlayer swPlayer;
private final T component;
}
@Delegate
@Getter
private final Player player;
private final Map<Class<? extends Component>, Component> components = new HashMap<>();
@@ -1,82 +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.core.events;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.core.Core;
import de.steamwar.linkage.Linked;
import de.steamwar.sql.SWException;
import de.steamwar.techhider.ProtocolUtils;
import net.minecraft.core.BlockPos;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Linked
public class AntiNocom implements Listener {
private final Map<Player, Integer> flags = new ConcurrentHashMap<>();
public AntiNocom() {
TinyProtocol.instance.addFilter(ServerboundPlayerActionPacket.class, this::onDig);
registerUseItem();
}
@EventHandler
public void onQuit(PlayerQuitEvent e) {
flags.remove(e.getPlayer());
}
private void registerUseItem() {
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (player, packet) -> {
BlockPos pos = packet.getHitResult().getBlockPos();
return isValid(player, "UseItem", pos.getX(), pos.getZ()) ? packet : null;
});
}
private ServerboundPlayerActionPacket onDig(Player player, ServerboundPlayerActionPacket packet) {
BlockPos pos = packet.getPos();
return isValid(player, "Dig", pos.getX(), pos.getZ()) ? packet : null;
}
private boolean isValid(Player player, String type, int x, int z) {
if ((x == 0 && z == 0) || player.getWorld().isChunkLoaded(ProtocolUtils.posToChunk(x), ProtocolUtils.posToChunk(z))) {
return true;
}
int amount = flags.compute(player, (p, a) -> a == null ? 1 : a + 1);
if (amount % 8 == 0) { // Only after 8 and every 8 to reduce false flags and spam
if (amount == 8) {
// Schedule player kick only once
Bukkit.getScheduler().runTask(Core.getInstance(), () -> player.kickPlayer(null));
}
SWException.log(player.getName() + " kicked for potential NoCom-DOS attack", x + " " + z + " " + type + " " + amount);
}
return false;
}
}
@@ -0,0 +1,73 @@
/*
* 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.core.worldedit;
import de.steamwar.command.SWCommand;
import lombok.experimental.UtilityClass;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
@UtilityClass
public class SteamEdit {
private static final AtomicBoolean INITIALIZED = new AtomicBoolean();
public static void init() {
INITIALIZED.set(true);
new Command();
new SteamEditWand();
new WorldEditRendererCUIEditor();
}
public boolean isInitialized() {
return INITIALIZED.get();
}
public static Function<Location, AdjacentFilter> ADJACENT_FILTERS = location -> AdjacentFilter.ALWAYS;
public interface AdjacentFilter {
AdjacentFilter ALWAYS = (x, y, z) -> true;
boolean contains(int x, int y, int z);
}
private static final class Command extends SWCommand {
public Command() {
super("steamwand", "steamedit", "steameditwand", "swwand", "sweditwand", "swedit");
}
@Register
public void get(Player player) {
if (player.getInventory().contains(SteamEditWand.ITEM)) {
player.getInventory().remove(SteamEditWand.ITEM);
}
ItemStack handItem = player.getInventory().getItemInMainHand();
player.getInventory().setItemInMainHand(SteamEditWand.ITEM);
if (!handItem.isEmpty()) {
player.getInventory().addItem(handItem);
}
}
}
}
@@ -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.core.worldedit;
import com.sk89q.worldedit.math.BlockVector3;
import de.steamwar.core.Core;
import org.bukkit.Location;
import org.bukkit.block.BlockFace;
import org.bukkit.util.Vector;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.stream.Stream;
public class SteamEditSelectAdjacent {
private static final BlockFace[] FACES = {
BlockFace.NORTH,
BlockFace.SOUTH,
BlockFace.EAST,
BlockFace.WEST,
BlockFace.UP,
BlockFace.DOWN,
};
private Location min;
private Location max;
private SteamEdit.AdjacentFilter filter;
private LinkedHashSet<Location> frontier = new LinkedHashSet<>();
public SteamEditSelectAdjacent(Location origin) {
// TODO: Fix the flood fill algorithm!
min = origin.clone();
max = origin.clone();
frontier.add(origin);
filter = SteamEdit.ADJACENT_FILTERS.apply(origin);
long time = System.currentTimeMillis();
for (int i = 0; i < 500_000 && !frontier.isEmpty(); i++) {
if (System.currentTimeMillis() - time > 1000) break;
expandNext();
}
Core.getInstance().getLogger().finer(System.currentTimeMillis() - time + "ms " + frontier.size() + " " + min + " to " + max);
}
public BlockVector3 pos1() {
return BlockVector3.at(min.getBlockX(), min.getBlockY(), min.getBlockZ());
}
public BlockVector3 pos2() {
return BlockVector3.at(max.getBlockX(), max.getBlockY(), max.getBlockZ());
}
private boolean inside(Location loc, int offset) {
return loc.getBlockX() >= min.getBlockX() + offset && loc.getBlockX() <= max.getBlockX() - offset &&
loc.getBlockY() >= min.getBlockY() + offset && loc.getBlockY() <= max.getBlockY() - offset &&
loc.getBlockZ() >= min.getBlockZ() + offset && loc.getBlockZ() <= max.getBlockZ() - offset;
}
public void expandNext() {
Location location = frontier.removeFirst();
expand(location);
}
private void expand(Location loc) {
for (BlockFace face : FACES) {
Vector direction = face.getDirection();
Location location = loc.clone().add(direction);
if (!filter.contains(location.getBlockX(), location.getBlockY(), location.getBlockZ())) continue;
if (inside(location, 0)) continue;
List<Location> layer = Stream.concat(selectFrontier(direction.getBlockX() == 0 ? null : loc.getBlockX(), direction.getBlockY() == 0 ? null : loc.getBlockY(), direction.getBlockZ() == 0 ? null : loc.getBlockZ())
.map(Location::clone)
.map(l -> l.add(direction)), Stream.of(location))
.filter(l -> !l.getBlock().isEmpty())
.filter(l -> !l.getBlock().isLiquid())
.toList();
if (layer.isEmpty()) continue;
if (direction.getBlockX() < 0 || direction.getBlockY() < 0 || direction.getBlockZ() < 0) {
min.add(direction);
} else {
max.add(direction);
}
layer.stream().filter(l -> !inside(l, 1))
.filter(l -> filter.contains(l.getBlockX(), l.getBlockY(), l.getBlockZ()))
.forEach(frontier::add);
}
}
private Stream<Location> selectFrontier(Integer x, Integer y, Integer z) {
Stream<Location> stream = frontier.stream();
if (x != null) stream = stream.filter(loc -> loc.getBlockX() == x);
if (y != null) stream = stream.filter(loc -> loc.getBlockY() == y);
if (z != null) stream = stream.filter(loc -> loc.getBlockZ() == z);
return stream;
}
}
@@ -0,0 +1,359 @@
/*
* 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.core.worldedit;
import com.comphenix.tinyprotocol.TinyProtocol;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.limit.PermissiveSelectorLimits;
import com.sk89q.worldedit.world.World;
import de.steamwar.core.Core;
import de.steamwar.core.SWPlayer;
import de.steamwar.inventory.SWItem;
import net.minecraft.core.BlockPos;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import org.bukkit.*;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.RayTraceResult;
import java.util.List;
public class SteamEditWand implements Listener {
public static final Material MATERIAL = Material.GOLDEN_AXE;
public static final String NAME = "§eSteam§8Edit §7Wand";
public static final String[] LORE = {
"§eL-Click§8: §7Set Pos1",
"§eR-Click§8: §7Set Pos2",
"§eShift §8+ §eR-Click§8: §7Set Pos2 or Expand Selection",
"§eM-Click§8: §7Select connected",
"§eSwap-Hands§8: §7Open CUI"
};
public static final ItemStack ITEM = new SWItem(MATERIAL, NAME).setLore(LORE).getItemStack();
static {
ItemMeta itemMeta = ITEM.getItemMeta();
itemMeta.addAttributeModifier(Attribute.BLOCK_INTERACTION_RANGE, new AttributeModifier(
NamespacedKey.fromString("range", Core.getInstance()),
35.0,
AttributeModifier.Operation.ADD_NUMBER
));
ITEM.setItemMeta(itemMeta);
}
private boolean noWand(Player player) {
return player.getInventory().getItemInMainHand().getType() != SteamEditWand.MATERIAL;
}
private final WorldEditPlugin we;
SteamEditWand() {
we = WorldEditWrapper.getWorldEditPlugin();
Bukkit.getPluginManager().registerEvents(this, Core.getInstance());
TinyProtocol.instance.addFilter(ServerboundSwingPacket.class, this::leftClick);
TinyProtocol.instance.addFilter(ServerboundInteractPacket.class, (player, packet) -> {
if (packet.isAttack()) {
return leftClick(player, packet);
} else {
return rightClick(player, packet);
}
});
TinyProtocol.instance.addFilter(ServerboundUseItemPacket.class, this::rightClick);
TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, this::rightClick);
TinyProtocol.instance.addFilter(ServerboundPickItemFromBlockPacket.class, this::middleClick);
TinyProtocol.instance.addFilter(ServerboundPlayerActionPacket.class, this::swapHands);
}
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if (noWand(event.getPlayer())) return;
event.setCancelled(true);
}
private Packet<?> leftClick(Player player, Packet<?> packet) {
if (noWand(player)) return packet;
if (player.getOpenInventory().getType() != InventoryType.CREATIVE) return packet;
double distance = WorldEditRendererCUIEditor.Type.CURSOR.getWidth(player).blocks;
RayTraceResult result = player.rayTraceBlocks(distance, FluidCollisionMode.NEVER);
if (result == null) return packet;
Block block = result.getHitBlock();
if (block == null) return packet;
BlockVector3 pos1 = BlockVector3.at(block.getX(), block.getY(), block.getZ());
setPos1(player, pos1);
return null;
}
private void setPos1(Player player, BlockVector3 pos1) {
LocalSession session = we.getSession(player);
World world = session.getSelectionWorld();
if (world == null) return;
selectPos1(session.getRegionSelector(world), player, session, pos1);
}
private Packet<?> rightClick(Player player, Packet<?> packet) {
if (noWand(player)) return packet;
double distance = WorldEditRendererCUIEditor.Type.CURSOR.getWidth(player).blocks;
RayTraceResult result = player.rayTraceBlocks(distance, FluidCollisionMode.NEVER);
if (result == null) return packet;
Block block = result.getHitBlock();
if (block == null) return packet;
BlockVector3 pos2 = BlockVector3.at(block.getX(), block.getY(), block.getZ());
if (!player.isSneaking()) {
setPos2(player, pos2);
} else {
setPos2OrExpand(player, pos2);
}
return null;
}
private void setPos2(Player player, BlockVector3 pos2) {
LocalSession session = we.getSession(player);
World world = session.getSelectionWorld();
if (world == null) return;
selectPos2(session.getRegionSelector(world), player, session, pos2);
}
private void setPos2OrExpand(Player player, BlockVector3 expand) {
LocalSession session = we.getSession(player);
World world = session.getSelectionWorld();
if (world == null) return;
RegionSelector regionSelector = session.getRegionSelector(world);
// Only primary set for now
if (!regionSelector.isDefined()) {
selectPos2(session.getRegionSelector(world), player, session, expand);
return;
}
List<BlockVector3> vertices = regionSelector.getVertices();
// Probably not a cuboid selection!
if (vertices.size() > 2) {
selectPos2(session.getRegionSelector(world), player, session, expand);
return;
}
BlockVector3 pos1 = vertices.get(0);
BlockVector3 pos2 = vertices.get(1);
BlockVector3 min = pos1.getMinimum(pos2);
BlockVector3 max = pos1.getMaximum(pos2);
if (expand.containedWithin(pos1, pos2)) {
return;
}
Closer x = Closer.UNKNOWN;
Closer y = Closer.UNKNOWN;
Closer z = Closer.UNKNOWN;
boolean insideX = min.x() < expand.x() && max.x() > expand.x();
boolean insideY = min.y() < expand.y() && max.y() > expand.y();
boolean insideZ = min.z() < expand.z() && max.z() > expand.z();
if (insideX) {
// No Change needed
} else if (Math.abs(pos1.x() - expand.x()) > Math.abs(pos2.x() - expand.x())) {
x = Closer.POS2;
} else if (Math.abs(pos1.x() - expand.x()) < Math.abs(pos2.x() - expand.x())) {
x = Closer.POS1;
}
if (insideY) {
// No Change needed
} else if (Math.abs(pos1.y() - expand.y()) > Math.abs(pos2.y() - expand.y())) {
y = Closer.POS2;
} else if (Math.abs(pos1.y() - expand.y()) < Math.abs(pos2.y() - expand.y())) {
y = Closer.POS1;
}
if (insideZ) {
// No Change needed
} else if (Math.abs(pos1.z() - expand.z()) > Math.abs(pos2.z() - expand.z())) {
z = Closer.POS2;
} else if (Math.abs(pos1.z() - expand.z()) < Math.abs(pos2.z() - expand.z())) {
z = Closer.POS1;
}
Closer x1 = Closer.UNKNOWN;
Closer y1 = Closer.UNKNOWN;
Closer z1 = Closer.UNKNOWN;
if (!insideX && pos1.x() != expand.x() && pos2.x() != expand.x() && x == Closer.UNKNOWN) {
x1 = y.and(z).or(Closer.POS1);
}
if (!insideY && pos1.y() != expand.y() && pos2.y() != expand.y() && y == Closer.UNKNOWN) {
y1 = x.and(z).or(Closer.POS1);
}
if (!insideZ && pos1.z() != expand.z() && pos2.z() != expand.z() && z == Closer.UNKNOWN) {
z1 = x.and(y).or(Closer.POS1);
}
if (x1 != Closer.UNKNOWN) x = x1;
if (y1 != Closer.UNKNOWN) y = y1;
if (z1 != Closer.UNKNOWN) z = z1;
boolean p1updated = false;
boolean p2updated = false;
if (x == Closer.POS1) {
pos1 = pos1.withX(expand.x());
p1updated = true;
} else if (x == Closer.POS2) {
pos2 = pos2.withX(expand.x());
p2updated = true;
}
if (y == Closer.POS1) {
pos1 = pos1.withY(expand.y());
p1updated = true;
} else if (y == Closer.POS2) {
pos2 = pos2.withY(expand.y());
p2updated = true;
}
if (z == Closer.POS1) {
pos1 = pos1.withZ(expand.z());
p1updated = true;
} else if (z == Closer.POS2) {
pos2 = pos2.withZ(expand.z());
p2updated = true;
}
if (p1updated) selectPos1(session.getRegionSelector(world), player, session, pos1);
if (p2updated) selectPos2(session.getRegionSelector(world), player, session, pos2);
}
private enum Closer {
POS1,
POS2,
UNKNOWN,
;
public Closer and(Closer closer) {
if (this == closer) {
return this;
} else if (this == UNKNOWN) {
return closer;
} else if (closer == UNKNOWN) {
return this;
} else {
return UNKNOWN;
}
}
public Closer or(Closer closer) {
if (this == UNKNOWN) return closer;
return this;
}
}
private ServerboundPickItemFromBlockPacket middleClick(Player player, ServerboundPickItemFromBlockPacket packet) {
if (noWand(player)) return packet;
LocalSession session = we.getSession(player);
World world = session.getSelectionWorld();
if (world == null) return packet;
RegionSelector regionSelector = session.getRegionSelector(world);
BlockPos pos = packet.pos();
SteamEditSelectAdjacent selector = new SteamEditSelectAdjacent(new Location(player.getWorld(), pos.getX(), pos.getY(), pos.getZ()));
selectPos1(regionSelector, player, session, selector.pos1());
selectPos2(regionSelector, player, session, selector.pos2());
return null;
}
private static class Pos1Message implements SWPlayer.Component {
private BukkitTask task;
public Pos1Message(RegionSelector regionSelector, Player player, LocalSession session, BlockVector3 pos1) {
task = Bukkit.getScheduler().runTaskLater(Core.getInstance(), () -> {
regionSelector.explainPrimarySelection(BukkitAdapter.adapt(player), session, pos1);
SWPlayer.of(player).removeComponent(Pos1Message.class);
}, 1);
}
@Override
public void onUnmount(SWPlayer player) {
task.cancel();
}
}
private void selectPos1(RegionSelector regionSelector, Player player, LocalSession session, BlockVector3 pos1) {
try {
if (!pos1.equals(regionSelector.getPrimaryPosition())) {
SWPlayer.of(player).setComponent(new Pos1Message(regionSelector, player, session, pos1));
}
} catch (IncompleteRegionException e) {
SWPlayer.of(player).setComponent(new Pos1Message(regionSelector, player, session, pos1));
}
regionSelector.selectPrimary(pos1, PermissiveSelectorLimits.getInstance());
WorldEditRenderer.renderPlayer(player);
}
private static class Pos2Message implements SWPlayer.Component {
private BukkitTask task;
public Pos2Message(RegionSelector regionSelector, Player player, LocalSession session, BlockVector3 pos2) {
task = Bukkit.getScheduler().runTaskLater(Core.getInstance(), () -> {
regionSelector.explainSecondarySelection(BukkitAdapter.adapt(player), session, pos2);
SWPlayer.of(player).removeComponent(Pos1Message.class);
}, 1);
}
@Override
public void onUnmount(SWPlayer player) {
task.cancel();
}
}
private void selectPos2(RegionSelector regionSelector, Player player, LocalSession session, BlockVector3 pos2) {
try {
if (regionSelector.getVertices().size() == 2 && !pos2.equals(regionSelector.getVertices().get(1))) {
SWPlayer.of(player).setComponent(new Pos2Message(regionSelector, player, session, pos2));
}
} catch (IncompleteRegionException e) {
SWPlayer.of(player).setComponent(new Pos2Message(regionSelector, player, session, pos2));
}
regionSelector.selectSecondary(pos2, PermissiveSelectorLimits.getInstance());
WorldEditRenderer.renderPlayer(player);
}
private ServerboundPlayerActionPacket swapHands(Player player, ServerboundPlayerActionPacket packet) {
if (noWand(player)) return packet;
if (packet.getAction() != ServerboundPlayerActionPacket.Action.SWAP_ITEM_WITH_OFFHAND) return packet;
Bukkit.getScheduler().runTask(Core.getInstance(), () -> {
WorldEditRendererCUIEditor.open(player, true);
});
return null;
}
}

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