6
6
import com .eternalcode .core .injector .annotations .Inject ;
7
7
import com .eternalcode .core .injector .annotations .component .Controller ;
8
8
import com .eternalcode .core .notice .NoticeService ;
9
+ import com .github .benmanes .caffeine .cache .Cache ;
10
+ import com .github .benmanes .caffeine .cache .Caffeine ;
11
+ import com .github .benmanes .caffeine .cache .RemovalCause ;
12
+ import com .github .benmanes .caffeine .cache .RemovalListener ;
9
13
import java .time .Duration ;
10
- import java .util .HashMap ;
11
- import java .util .Map ;
12
14
import java .util .UUID ;
15
+ import java .util .concurrent .TimeUnit ;
16
+ import org .bukkit .Bukkit ;
13
17
import org .bukkit .GameMode ;
14
18
import org .bukkit .Material ;
15
19
import org .bukkit .Tag ;
27
31
import org .bukkit .event .player .PlayerTeleportEvent .TeleportCause ;
28
32
import org .bukkit .inventory .CraftingInventory ;
29
33
import org .bukkit .util .Vector ;
34
+ import org .jetbrains .annotations .NotNull ;
30
35
31
36
/**
32
37
* Controller allowing vanished players to silently access containers
33
38
* without triggering animations/sounds for other players.
34
39
* Temporarily switches player to Spectator mode for 1 tick.
35
40
* <p>
41
+ * Uses Caffeine cache for automatic cleanup and state management.
42
+ * <p>
36
43
* Implementation based on SayanVanish plugin approach.
37
44
* Original idea: <a href="https://github.com/Syrent/SayanVanish/blob/master/sayanvanish-bukkit/src/main/kotlin/org/sayandev/sayanvanish/bukkit/feature/features/FeatureSilentContainer.kt">...</a>
38
45
*/
@@ -41,24 +48,31 @@ class OpenSilentController implements Listener {
41
48
42
49
private static final Vector ZERO_VELOCITY = new Vector (0.0 , 0.0 , 0.0 );
43
50
private static final int TICK_DURATION_MILLIS = 50 ;
51
+ private static final int CACHE_EXPIRE_SECONDS = 5 ;
44
52
45
53
private final NoticeService noticeService ;
46
54
private final VanishService vanishService ;
47
55
private final VanishSettings config ;
48
56
private final Scheduler scheduler ;
49
57
50
- private final Map <UUID , ContainerWrapper > containerCache = new HashMap <>() ;
58
+ private final Cache <UUID , ContainerWrapper > containerCache ;
51
59
52
60
@ Inject
53
61
OpenSilentController (
54
62
NoticeService noticeService ,
55
63
VanishService vanishService ,
56
64
VanishSettings config ,
57
- Scheduler scheduler ) {
65
+ Scheduler scheduler
66
+ ) {
58
67
this .noticeService = noticeService ;
59
68
this .vanishService = vanishService ;
60
69
this .config = config ;
61
70
this .scheduler = scheduler ;
71
+
72
+ this .containerCache = Caffeine .newBuilder ()
73
+ .expireAfterWrite (CACHE_EXPIRE_SECONDS , TimeUnit .SECONDS )
74
+ .removalListener (new PlayerRestoreListener (scheduler ))
75
+ .build ();
62
76
}
63
77
64
78
@ EventHandler (priority = EventPriority .HIGHEST , ignoreCancelled = true )
@@ -77,7 +91,8 @@ void handlePlayerInteract(PlayerInteractEvent event) {
77
91
event .setCancelled (true );
78
92
this .noticeService .player (
79
93
player .getUniqueId (),
80
- message -> message .vanish ().cantOpenInventoryWhileVanished ());
94
+ message -> message .vanish ().cantOpenInventoryWhileVanished ()
95
+ );
81
96
return ;
82
97
}
83
98
@@ -98,15 +113,9 @@ void handlePlayerInteract(PlayerInteractEvent event) {
98
113
return ;
99
114
}
100
115
101
- ContainerWrapper playerData =
102
- new ContainerWrapper (player .getGameMode (), player .getAllowFlight (), player .isFlying ());
103
- this .containerCache .put (player .getUniqueId (), playerData );
104
-
105
- this .switchToSpectator (player );
106
- this .restoreAfterTick (player );
116
+ this .switchForOneTick (player );
107
117
}
108
118
109
- // Blocks unwanted teleports while in temporary spectator mode
110
119
@ EventHandler (priority = EventPriority .HIGHEST )
111
120
private void handlePlayerTeleport (PlayerTeleportEvent event ) {
112
121
Player player = event .getPlayer ();
@@ -116,27 +125,24 @@ private void handlePlayerTeleport(PlayerTeleportEvent event) {
116
125
return ;
117
126
}
118
127
119
- if (!this .containerCache .containsKey (player .getUniqueId ())
120
- && cause != PlayerTeleportEvent .TeleportCause .SPECTATE ) {
121
- return ;
128
+ if (this .containerCache .getIfPresent (player .getUniqueId ()) != null ) {
129
+ event .setCancelled (true );
122
130
}
123
-
124
- event .setCancelled (true );
125
131
}
126
132
127
133
@ EventHandler (priority = EventPriority .HIGHEST )
128
134
private void handlePlayerQuit (PlayerQuitEvent event ) {
129
135
Player player = event .getPlayer ();
130
- ContainerWrapper data = this . containerCache . get ( player .getUniqueId () );
136
+ UUID playerId = player .getUniqueId ();
131
137
138
+ ContainerWrapper data = this .containerCache .getIfPresent (playerId );
132
139
if (data != null ) {
140
+ this .containerCache .invalidate (playerId );
133
141
data .apply (player );
134
142
}
135
-
136
- this .containerCache .remove (player .getUniqueId ());
137
143
}
138
144
139
- @ EventHandler
145
+ @ EventHandler ( priority = EventPriority . MONITOR )
140
146
private void handleInventoryClose (InventoryCloseEvent event ) {
141
147
if (!(event .getPlayer () instanceof Player player )) {
142
148
return ;
@@ -150,29 +156,42 @@ private void handleInventoryClose(InventoryCloseEvent event) {
150
156
return ;
151
157
}
152
158
153
- ContainerWrapper playerData =
154
- new ContainerWrapper (player .getGameMode (), player .getAllowFlight (), player .isFlying ());
155
- this .containerCache .put (player .getUniqueId (), playerData );
156
-
157
- this .switchToSpectator (player );
158
- this .restoreAfterTick (player );
159
+ if (this .containerCache .getIfPresent (player .getUniqueId ()) == null ) {
160
+ this .switchForOneTick (player );
161
+ }
159
162
}
160
163
161
- private void switchToSpectator (Player player ) {
164
+ private void switchForOneTick (Player player ) {
165
+ UUID playerId = player .getUniqueId ();
166
+
167
+ if (this .containerCache .getIfPresent (playerId ) != null ) {
168
+ return ;
169
+ }
170
+
171
+ ContainerWrapper playerData = new ContainerWrapper (
172
+ player .getGameMode (),
173
+ player .getAllowFlight (),
174
+ player .isFlying ()
175
+ );
176
+ this .containerCache .put (playerId , playerData );
177
+
162
178
player .setAllowFlight (true );
163
179
player .setFlying (true );
164
180
player .setVelocity (ZERO_VELOCITY );
165
181
player .setGameMode (GameMode .SPECTATOR );
182
+ this .scheduler .runLater (() -> this .restorePlayer (playerId ), Duration .ofMillis (TICK_DURATION_MILLIS ));
166
183
}
167
184
168
- private void restoreAfterTick (Player player ) {
169
- this .scheduler .runLater (
170
- () -> {
171
- ContainerWrapper data = this .containerCache .remove (player .getUniqueId ());
172
- if (data != null ) {
173
- data .apply (player );
174
- }
175
- }, Duration .ofMillis (TICK_DURATION_MILLIS ));
185
+ private void restorePlayer (UUID playerId ) {
186
+ ContainerWrapper data = this .containerCache .getIfPresent (playerId );
187
+ if (data != null ) {
188
+ this .containerCache .invalidate (playerId );
189
+
190
+ Player player = Bukkit .getPlayer (playerId );
191
+ if (player != null && player .isOnline ()) {
192
+ data .apply (player );
193
+ }
194
+ }
176
195
}
177
196
178
197
private boolean isContainerType (Material type ) {
@@ -182,11 +201,29 @@ private boolean isContainerType(Material type) {
182
201
};
183
202
}
184
203
204
+ private record PlayerRestoreListener (Scheduler scheduler ) implements RemovalListener <UUID , ContainerWrapper > {
205
+
206
+ @ Override
207
+ public void onRemoval (UUID playerId , ContainerWrapper data , @ NotNull RemovalCause cause ) {
208
+ if (cause == RemovalCause .EXPIRED && data != null ) {
209
+
210
+ this .scheduler .run (() -> {
211
+ Player player = Bukkit .getPlayer (playerId );
212
+ if (player != null && player .isOnline ()) {
213
+ data .apply (player );
214
+ }
215
+ });
216
+ }
217
+ }
218
+ }
219
+
185
220
private record ContainerWrapper (GameMode gameMode , boolean allowFlight , boolean isFlying ) {
186
221
public void apply (Player player ) {
187
- player .setGameMode (gameMode );
188
- player .setAllowFlight (allowFlight );
189
- player .setFlying (isFlying );
222
+ if (player .isOnline ()) {
223
+ player .setGameMode (gameMode );
224
+ player .setAllowFlight (allowFlight );
225
+ player .setFlying (isFlying );
226
+ }
190
227
}
191
228
}
192
229
}
0 commit comments