3131import com .dre .brewery .utility .MaterialUtil ;
3232import com .dre .brewery .utility .MinecraftVersion ;
3333import com .dre .brewery .utility .Tuple ;
34+ import com .github .Anon8281 .universalScheduler .scheduling .tasks .MyScheduledTask ;
3435import lombok .Getter ;
3536import lombok .Setter ;
3637import org .bukkit .Color ;
5657import java .util .HashSet ;
5758import java .util .List ;
5859import java .util .Map ;
59- import java .util .Random ;
6060import java .util .Set ;
6161import java .util .UUID ;
6262import java .util .concurrent .ConcurrentHashMap ;
63+ import java .util .concurrent .ThreadLocalRandom ;
6364
6465@ Getter
6566@ Setter
@@ -69,7 +70,6 @@ public class BCauldron {
6970 private static final Config config = ConfigManager .getConfig (Config .class );
7071 private static final Lang lang = ConfigManager .getConfig (Lang .class );
7172 public static final int PARTICLEPAUSE = 15 ;
72- public static Random particleRandom = new Random ();
7373 private static final Set <UUID > plInteracted = new HashSet <>(); // Interact Event helper
7474 @ Getter
7575 public static Map <Block , BCauldron > bcauldrons = new ConcurrentHashMap <>(); // All active cauldrons. Mapped to their block for fast retrieve
@@ -82,6 +82,7 @@ public class BCauldron {
8282 private Color particleColor ;
8383 private final Location particleLocation ;
8484 private final UUID id ;
85+ private volatile MyScheduledTask foliaParticleTask ;
8586
8687 public BCauldron (Block block ) {
8788 this .block = block ;
@@ -181,6 +182,7 @@ public static boolean ingredientAdd(Block block, ItemStack ingredient, Player pl
181182 if (bcauldron == null ) {
182183 bcauldron = new BCauldron (block );
183184 BCauldron .bcauldrons .put (block , bcauldron );
185+ bcauldron .startFoliaParticleTask ();
184186 }
185187
186188 IngedientAddEvent event = new IngedientAddEvent (player , block , bcauldron , ingredient .clone (), rItem );
@@ -208,20 +210,20 @@ public boolean fill(Player player, Block block) {
208210 if (VERSION .isOrLater (MinecraftVersion .V1_13 )) {
209211 BlockData data = block .getBlockData ();
210212 if (!(data instanceof Levelled )) {
211- bcauldrons . remove (block );
213+ remove (block );
212214 return false ;
213215 }
214216 Levelled cauldron = ((Levelled ) data );
215217 if (cauldron .getLevel () <= 0 ) {
216- bcauldrons . remove (block );
218+ remove (block );
217219 return false ;
218220 }
219221
220222 // If the Water_Cauldron type exists and the cauldron is on last level
221223 if (MaterialUtil .WATER_CAULDRON != null && cauldron .getLevel () == 1 ) {
222224 // Empty Cauldron
223225 block .setType (Material .CAULDRON );
224- bcauldrons . remove (block );
226+ remove (block );
225227 } else {
226228 cauldron .setLevel (cauldron .getLevel () - 1 );
227229
@@ -231,7 +233,7 @@ public boolean fill(Player player, Block block) {
231233 block .setBlockData (data );
232234
233235 if (cauldron .getLevel () <= 0 ) {
234- bcauldrons . remove (block );
236+ remove (block );
235237 } else {
236238 changed = true ;
237239 }
@@ -243,14 +245,14 @@ public boolean fill(Player player, Block block) {
243245 if (data > 3 ) {
244246 data = 3 ;
245247 } else if (data <= 0 ) {
246- bcauldrons . remove (block );
248+ remove (block );
247249 return false ;
248250 }
249251 data -= 1 ;
250252 MaterialUtil .setData (block , data );
251253
252254 if (data == 0 ) {
253- bcauldrons . remove (block );
255+ remove (block );
254256 } else {
255257 changed = true ;
256258 }
@@ -284,6 +286,8 @@ public static void printTime(Player player, Block block) {
284286 }
285287
286288 public void cookEffect () {
289+ assert !VERSION .isFolia () || BreweryPlugin .getScheduler ().isRegionThread (block .getLocation ())
290+ : "cookEffect must run on owning region thread" ;
287291 if (BUtil .isChunkLoaded (block ) && MaterialUtil .isCauldronHeatSource (block .getRelative (BlockFace .DOWN ))) {
288292 Color color = getParticleColor ();
289293 // Colorable spirally spell, 0 count enables color instead of the offset variables
@@ -304,17 +308,17 @@ public void cookEffect() {
304308 return ;
305309 }
306310
307- if (particleRandom . nextFloat () > 0.85 ) {
311+ if (ThreadLocalRandom . current (). nextFloat () > 0.85f ) {
308312 // Dark pixely smoke cloud at 0.4 random in x and z
309313 // 0 count enables direction, send to y = 1 with speed 0.09
310314 block .getWorld ().spawnParticle (BukkitConstants .LARGE_SMOKE , getRandParticleLoc (), 0 , 0 , 1 , 0 , 0.09 );
311315 }
312- if (particleRandom . nextFloat () > 0.2 ) {
316+ if (ThreadLocalRandom . current (). nextFloat () > 0.2f ) {
313317 // A Water Splash with 0.2 offset in x and z
314318 block .getWorld ().spawnParticle (BukkitConstants .SPLASH , particleLocation , 1 , 0.2 , 0 , 0.2 );
315319 }
316320
317- if (VERSION .isOrLater (MinecraftVersion .V1_13 ) && particleRandom . nextFloat () > 0.4 ) {
321+ if (VERSION .isOrLater (MinecraftVersion .V1_13 ) && ThreadLocalRandom . current (). nextFloat () > 0.4f ) {
318322 // Two hovering pixely dust clouds, a bit of offset and with DustOptions to give some color and size
319323 block .getWorld ().spawnParticle (BukkitConstants .DUST , particleLocation , 2 , 0.15 , 0.2 , 0.15 , new Particle .DustOptions (color , 1.5f ));
320324 }
@@ -323,9 +327,9 @@ public void cookEffect() {
323327
324328 private Location getRandParticleLoc () {
325329 return new Location (particleLocation .getWorld (),
326- particleLocation .getX () + (particleRandom .nextDouble () * 0.8 ) - 0.4 ,
330+ particleLocation .getX () + (ThreadLocalRandom . current () .nextDouble () * 0.8 ) - 0.4 ,
327331 particleLocation .getY (),
328- particleLocation .getZ () + (particleRandom .nextDouble () * 0.8 ) - 0.4 );
332+ particleLocation .getZ () + (ThreadLocalRandom . current () .nextDouble () * 0.8 ) - 0.4 );
329333 }
330334
331335 /**
@@ -404,19 +408,71 @@ public Color getParticleColor() {
404408 }
405409
406410 public static void processCookEffects () {
411+ if (VERSION .isFolia ()) return ;
407412 if (!config .isEnableCauldronParticles ()) return ;
408413 if (bcauldrons .isEmpty ()) {
409414 return ;
410415 }
411416 final float chance = 1f / PARTICLEPAUSE ;
412417
413418 for (BCauldron cauldron : bcauldrons .values ()) {
414- if (particleRandom .nextFloat () < chance ) {
419+ if (ThreadLocalRandom . current () .nextFloat () < chance ) {
415420 BreweryPlugin .getScheduler ().runTask (cauldron .block .getLocation (), cauldron ::cookEffect );
416421 }
417422 }
418423 }
419424
425+ public void startFoliaParticleTask () {
426+ if (!VERSION .isFolia () || !config .isEnableCauldronParticles ()) {
427+ return ;
428+ }
429+ synchronized (this ) {
430+ if (foliaParticleTask != null && !foliaParticleTask .isCancelled ()) {
431+ return ;
432+ }
433+ long delay = ThreadLocalRandom .current ().nextLong (1 , PARTICLEPAUSE + 1L );
434+ foliaParticleTask = BreweryPlugin .getScheduler ().runTaskTimer (block .getLocation (), () -> {
435+ if (!config .isEnableCauldronParticles ()) {
436+ return ;
437+ }
438+ if (config .isMinimalParticles () && ThreadLocalRandom .current ().nextFloat () > 0.5f ) {
439+ return ;
440+ }
441+ cookEffect ();
442+ }, delay , PARTICLEPAUSE );
443+ }
444+ }
445+
446+ public void stopFoliaParticleTask () {
447+ if (!VERSION .isFolia ()) {
448+ return ;
449+ }
450+ synchronized (this ) {
451+ if (foliaParticleTask != null ) {
452+ foliaParticleTask .cancel ();
453+ foliaParticleTask = null ;
454+ }
455+ }
456+ }
457+
458+ public static void startAllFoliaParticleTasks () {
459+ if (!VERSION .isFolia () || !config .isEnableCauldronParticles ()) {
460+ return ;
461+ }
462+ for (BCauldron cauldron : bcauldrons .values ()) {
463+ cauldron .startFoliaParticleTask ();
464+ }
465+ }
466+
467+ public static void stopAllFoliaParticleTasks () {
468+ if (!VERSION .isFolia ()) {
469+ return ;
470+ }
471+ for (BCauldron cauldron : bcauldrons .values ()) {
472+ cauldron .stopFoliaParticleTask ();
473+ }
474+ }
475+
420476 public static void clickCauldron (PlayerInteractEvent event ) {
421477 Material materialInHand = event .getMaterial ();
422478 ItemStack item = event .getItem ();
@@ -529,8 +585,10 @@ public static void clickCauldron(PlayerInteractEvent event) {
529585 */
530586 public static void reload () {
531587 if (!config .isEnableCauldronParticles ()) {
588+ stopAllFoliaParticleTasks ();
532589 return ;
533590 }
591+ startAllFoliaParticleTasks ();
534592
535593 var scheduler = BreweryPlugin .getScheduler ();
536594 for (BCauldron cauldron : bcauldrons .values ()) {
@@ -549,7 +607,12 @@ public static void reload() {
549607 * reset to normal cauldron
550608 */
551609 public static boolean remove (Block block ) {
552- return bcauldrons .remove (block ) != null ;
610+ BCauldron removed = bcauldrons .remove (block );
611+ if (removed != null ) {
612+ removed .stopFoliaParticleTask ();
613+ return true ;
614+ }
615+ return false ;
553616 }
554617
555618 /**
@@ -562,15 +625,21 @@ public static boolean hasDataInWorld(World world) {
562625 // unloads cauldrons that are in a unloading world
563626 // as they were written to file just before, this is safe to do
564627 public static void onUnload (World world ) {
565- bcauldrons .keySet ().removeIf (block -> block .getWorld ().equals (world ));
628+ List <Block > blocksToRemove = bcauldrons .keySet ().stream ()
629+ .filter (block -> block .getWorld ().equals (world ))
630+ .toList ();
631+ blocksToRemove .forEach (BCauldron ::remove );
566632 }
567633
568634 /**
569635 * Unload all Cauldrons that have are in a unloaded World
570636 */
571637 public static void unloadWorlds () {
572638 List <World > worlds = BreweryPlugin .getInstance ().getServer ().getWorlds ();
573- bcauldrons .keySet ().removeIf (block -> !worlds .contains (block .getWorld ()));
639+ List <Block > blocksToRemove = bcauldrons .keySet ().stream ()
640+ .filter (block -> !worlds .contains (block .getWorld ()))
641+ .toList ();
642+ blocksToRemove .forEach (BCauldron ::remove );
574643 }
575644
576645 public static void save (ConfigurationSection config , ConfigurationSection oldData ) {
0 commit comments