aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0823-fixup-MC-Utils.patch
blob: 60653c117b077a7f3d464899cef5f5252ac90b2e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 21 Oct 2024 12:21:54 -0700
Subject: [PATCH] fixup! MC Utils


diff --git a/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java
new file mode 100644
index 0000000000000000000000000000000000000000..6c98d420ea84c10ef4f15d4deb3f04e610ed8548
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java
@@ -0,0 +1,117 @@
+package ca.spottedleaf.moonrise.common;
+
+import com.mojang.datafixers.DSL;
+import com.mojang.datafixers.DataFixer;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ChunkHolder;
+import net.minecraft.server.level.GenerationChunkHolder;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.BlockGetter;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.ProtoChunk;
+import net.minecraft.world.level.chunk.storage.SerializableChunkData;
+import net.minecraft.world.level.entity.EntityTypeTest;
+import net.minecraft.world.phys.AABB;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.function.Predicate;
+
+public interface PlatformHooks {
+    public static PlatformHooks get() {
+        return Holder.INSTANCE;
+    }
+
+    public String getBrand();
+
+    public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos);
+
+    public Predicate<BlockState> maybeHasLightEmission();
+
+    public boolean hasCurrentlyLoadingChunk();
+
+    public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder);
+
+    public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk);
+
+    public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original);
+
+    public boolean allowAsyncTicketUpdates();
+
+    public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel);
+
+    public void chunkUnloadFromWorld(final LevelChunk chunk);
+
+    public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data);
+
+    public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player);
+
+    public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player);
+
+    public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate<? super Entity> predicate,
+                                 final List<Entity> into);
+
+    public <T extends Entity> void addToGetEntities(final Level world, final EntityTypeTest<Entity, T> entityTypeTest,
+                                                    final AABB boundingBox, final Predicate<? super T> predicate,
+                                                    final List<? super T> into, final int maxCount);
+
+    public void entityMove(final Entity entity, final long oldSection, final long newSection);
+
+    public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event);
+
+    public boolean configFixMC224294();
+
+    public boolean configAutoConfigSendDistance();
+
+    public double configPlayerMaxLoadRate();
+
+    public double configPlayerMaxGenRate();
+
+    public double configPlayerMaxSendRate();
+
+    public int configPlayerMaxConcurrentLoads();
+
+    public int configPlayerMaxConcurrentGens();
+
+    public long configAutoSaveInterval(final ServerLevel world);
+
+    public int configMaxAutoSavePerTick(final ServerLevel world);
+
+    public boolean configFixMC159283();
+
+    // support for CB chunk mustNotSave
+    public boolean forceNoSave(final ChunkAccess chunk);
+
+    public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
+                                  final int fromVersion, final int toVersion);
+
+    public boolean hasMainChunkLoadHook();
+
+    public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData);
+
+    public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities);
+
+    public void unloadEntity(final Entity entity);
+
+    public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk);
+
+    public int modifyEntityTrackingRange(final Entity entity, final int currentRange);
+
+    public static final class Holder {
+        private Holder() {
+        }
+
+        private static final PlatformHooks INSTANCE;
+
+        static {
+            INSTANCE = ServiceLoader.load(PlatformHooks.class, PlatformHooks.class.getClassLoader()).findFirst()
+                .orElseThrow(() -> new RuntimeException("Failed to locate PlatformHooks"));
+        }
+    }
+}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java
index ba68998f6ef57b24c72fd833bd7de440de9501cc..7fed43a1e7bcf35c4d7fd3224837a47fedd59860 100644
--- a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java
+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java
@@ -13,15 +13,15 @@ import java.util.NoSuchElementException;
  */
 public final class EntityList implements Iterable<Entity> {
 
-    protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f);
+    private final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f);
     {
         this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE);
     }
 
-    protected static final Entity[] EMPTY_LIST = new Entity[0];
+    private static final Entity[] EMPTY_LIST = new Entity[0];
 
-    protected Entity[] entities = EMPTY_LIST;
-    protected int count;
+    private Entity[] entities = EMPTY_LIST;
+    private int count;
 
     public int size() {
         return this.count;
@@ -94,10 +94,9 @@ public final class EntityList implements Iterable<Entity> {
 
     @Override
     public Iterator<Entity> iterator() {
-        return new Iterator<Entity>() {
-
-            Entity lastRet;
-            int current;
+        return new Iterator<>() {
+            private Entity lastRet;
+            private int current;
 
             @Override
             public boolean hasNext() {
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java
deleted file mode 100644
index fcfbca333234c09f7c056bbfcd9ac8860b20a8db..0000000000000000000000000000000000000000
--- a/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package ca.spottedleaf.moonrise.common.list;
-
-import it.unimi.dsi.fastutil.longs.LongIterator;
-import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap;
-import java.util.Arrays;
-import net.minecraft.world.level.block.Block;
-import net.minecraft.world.level.block.state.BlockState;
-import net.minecraft.world.level.chunk.GlobalPalette;
-
-public final class IBlockDataList {
-
-    private static final GlobalPalette<BlockState> GLOBAL_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY);
-
-    // map of location -> (index | (location << 16) | (palette id << 32))
-    private final Short2LongOpenHashMap map = new Short2LongOpenHashMap(2, 0.8f);
-    {
-        this.map.defaultReturnValue(Long.MAX_VALUE);
-    }
-
-    private static final long[] EMPTY_LIST = new long[0];
-
-    private long[] byIndex = EMPTY_LIST;
-    private int size;
-
-    public static int getLocationKey(final int x, final int y, final int z) {
-        return (x & 15) | (((z & 15) << 4)) | ((y & 255) << (4 + 4));
-    }
-
-    public static BlockState getBlockDataFromRaw(final long raw) {
-        return GLOBAL_PALETTE.valueFor((int)(raw >>> 32));
-    }
-
-    public static int getIndexFromRaw(final long raw) {
-        return (int)(raw & 0xFFFF);
-    }
-
-    public static int getLocationFromRaw(final long raw) {
-        return (int)((raw >>> 16) & 0xFFFF);
-    }
-
-    public static long getRawFromValues(final int index, final int location, final BlockState data) {
-        return (long)index | ((long)location << 16) | (((long)GLOBAL_PALETTE.idFor(data)) << 32);
-    }
-
-    public static long setIndexRawValues(final long value, final int index) {
-        return value & ~(0xFFFF) | (index);
-    }
-
-    public long add(final int x, final int y, final int z, final BlockState data) {
-        return this.add(getLocationKey(x, y, z), data);
-    }
-
-    public long add(final int location, final BlockState data) {
-        final long curr = this.map.get((short)location);
-
-        if (curr == Long.MAX_VALUE) {
-            final int index = this.size++;
-            final long raw = getRawFromValues(index, location, data);
-            this.map.put((short)location, raw);
-
-            if (index >= this.byIndex.length) {
-                this.byIndex = Arrays.copyOf(this.byIndex, (int)Math.max(4L, this.byIndex.length * 2L));
-            }
-
-            this.byIndex[index] = raw;
-            return raw;
-        } else {
-            final int index = getIndexFromRaw(curr);
-            final long raw = this.byIndex[index] = getRawFromValues(index, location, data);
-
-            this.map.put((short)location, raw);
-
-            return raw;
-        }
-    }
-
-    public long remove(final int x, final int y, final int z) {
-        return this.remove(getLocationKey(x, y, z));
-    }
-
-    public long remove(final int location) {
-        final long ret = this.map.remove((short)location);
-        final int index = getIndexFromRaw(ret);
-        if (ret == Long.MAX_VALUE) {
-            return ret;
-        }
-
-        // move the entry at the end to this index
-        final int endIndex = --this.size;
-        final long end = this.byIndex[endIndex];
-        if (index != endIndex) {
-            // not empty after this call
-            this.map.put((short)getLocationFromRaw(end), setIndexRawValues(end, index));
-        }
-        this.byIndex[index] = end;
-        this.byIndex[endIndex] = 0L;
-
-        return ret;
-    }
-
-    public int size() {
-        return this.size;
-    }
-
-    public long getRaw(final int index) {
-        return this.byIndex[index];
-    }
-
-    public int getLocation(final int index) {
-        return getLocationFromRaw(this.getRaw(index));
-    }
-
-    public BlockState getData(final int index) {
-        return getBlockDataFromRaw(this.getRaw(index));
-    }
-
-    public void clear() {
-        this.size = 0;
-        this.map.clear();
-    }
-
-    public LongIterator getRawIterator() {
-        return this.map.values().iterator();
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f3b25bb2439f283f878db93973a02fcdcd14eed
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java
@@ -0,0 +1,77 @@
+package ca.spottedleaf.moonrise.common.list;
+
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+import java.util.Arrays;
+
+public final class IntList {
+
+    private final Int2IntOpenHashMap map = new Int2IntOpenHashMap();
+    {
+        this.map.defaultReturnValue(Integer.MIN_VALUE);
+    }
+
+    private static final int[] EMPTY_LIST = new int[0];
+
+    private int[] byIndex = EMPTY_LIST;
+    private int count;
+
+    public int size() {
+        return this.count;
+    }
+
+    public void setMinCapacity(final int len) {
+        final int[] byIndex = this.byIndex;
+        if (byIndex.length < len) {
+            this.byIndex = Arrays.copyOf(byIndex, len);
+        }
+    }
+
+    public int getRaw(final int index) {
+        return this.byIndex[index];
+    }
+
+    public boolean add(final int value) {
+        final int count = this.count;
+        final int currIndex = this.map.putIfAbsent(value, count);
+
+        if (currIndex != Integer.MIN_VALUE) {
+            return false; // already in this list
+        }
+
+        int[] list = this.byIndex;
+
+        if (list.length == count) {
+            // resize required
+            list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative
+        }
+
+        list[count] = value;
+        this.count = count + 1;
+
+        return true;
+    }
+
+    public boolean remove(final int value) {
+        final int index = this.map.remove(value);
+        if (index == Integer.MIN_VALUE) {
+            return false;
+        }
+
+        // move the entry at the end to this index
+        final int endIndex = --this.count;
+        final int end = this.byIndex[endIndex];
+        if (index != endIndex) {
+            // not empty after this call
+            this.map.put(end, index);
+        }
+        this.byIndex[index] = end;
+        this.byIndex[endIndex] = 0;
+
+        return true;
+    }
+
+    public void clear() {
+        this.count = 0;
+        this.map.clear();
+    }
+}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java
new file mode 100644
index 0000000000000000000000000000000000000000..2bae9949ef325d0001aa638150fbbdf968367e75
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java
@@ -0,0 +1,77 @@
+package ca.spottedleaf.moonrise.common.list;
+
+import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap;
+import java.util.Arrays;
+
+public final class ShortList {
+
+    private final Short2ShortOpenHashMap map = new Short2ShortOpenHashMap();
+    {
+        this.map.defaultReturnValue(Short.MIN_VALUE);
+    }
+
+    private static final short[] EMPTY_LIST = new short[0];
+
+    private short[] byIndex = EMPTY_LIST;
+    private short count;
+
+    public int size() {
+        return (int)this.count;
+    }
+
+    public short getRaw(final int index) {
+        return this.byIndex[index];
+    }
+
+    public void setMinCapacity(final int len) {
+        final short[] byIndex = this.byIndex;
+        if (byIndex.length < len) {
+            this.byIndex = Arrays.copyOf(byIndex, len);
+        }
+    }
+
+    public boolean add(final short value) {
+        final int count = (int)this.count;
+        final short currIndex = this.map.putIfAbsent(value, (short)count);
+
+        if (currIndex != Short.MIN_VALUE) {
+            return false; // already in this list
+        }
+
+        short[] list = this.byIndex;
+
+        if (list.length == count) {
+            // resize required
+            list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative
+        }
+
+        list[count] = value;
+        this.count = (short)(count + 1);
+
+        return true;
+    }
+
+    public boolean remove(final short value) {
+        final short index = this.map.remove(value);
+        if (index == Short.MIN_VALUE) {
+            return false;
+        }
+
+        // move the entry at the end to this index
+        final short endIndex = --this.count;
+        final short end = this.byIndex[endIndex];
+        if (index != endIndex) {
+            // not empty after this call
+            this.map.put(end, index);
+        }
+        this.byIndex[(int)index] = end;
+        this.byIndex[(int)endIndex] = (short)0;
+
+        return true;
+    }
+
+    public void clear() {
+        this.count = (short)0;
+        this.map.clear();
+    }
+}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java
new file mode 100644
index 0000000000000000000000000000000000000000..c2d917c2eac55b8a4411a6e159f177f9428b1150
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java
@@ -0,0 +1,22 @@
+package ca.spottedleaf.moonrise.common.misc;
+
+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
+import java.lang.invoke.VarHandle;
+
+public final class LazyRunnable implements Runnable {
+
+    private volatile Runnable toRun;
+    private static final VarHandle TO_RUN_HANDLE = ConcurrentUtil.getVarHandle(LazyRunnable.class, "toRun", Runnable.class);
+
+    public void setRunnable(final Runnable run) {
+        final Runnable prev = (Runnable)TO_RUN_HANDLE.compareAndExchange(this, (Runnable)null, run);
+        if (prev != null) {
+            throw new IllegalStateException("Runnable already set");
+        }
+    }
+
+    @Override
+    public void run() {
+        ((Runnable)TO_RUN_HANDLE.getVolatile(this)).run();
+    }
+}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
index ab093b0e8ac6f762921eb1d15f5217345c4eba05..bb44de17a37082e57f2292a4f470740be1d09b11 100644
--- a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
@@ -4,13 +4,17 @@ import ca.spottedleaf.moonrise.common.list.ReferenceList;
 import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
 import ca.spottedleaf.moonrise.common.util.MoonriseConstants;
 import ca.spottedleaf.moonrise.common.util.ChunkSystem;
+import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
+import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
 import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants;
+import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
 import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
 import net.minecraft.core.BlockPos;
 import net.minecraft.server.level.ServerLevel;
 import net.minecraft.server.level.ServerPlayer;
 import net.minecraft.world.level.ChunkPos;
+import java.util.ArrayList;
 
 public final class NearbyPlayers {
 
@@ -20,7 +24,27 @@ public final class NearbyPlayers {
         GENERAL_REALLY_SMALL,
         TICK_VIEW_DISTANCE,
         VIEW_DISTANCE,
-        SPAWN_RANGE, // Moonrise - chunk tick iteration
+        // Moonrise start - chunk tick iteration
+        SPAWN_RANGE {
+            @Override
+            void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
+                ((ChunkTickServerLevel)world).moonrise$addPlayerTickingRequest(chunkX, chunkZ);
+            }
+
+            @Override
+            void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
+                ((ChunkTickServerLevel)world).moonrise$removePlayerTickingRequest(chunkX, chunkZ);
+            }
+        };
+        // Moonrise end - chunk tick iteration
+
+        void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
+
+        }
+
+        void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
+
+        }
     }
 
     private static final NearbyMapType[] MAP_TYPES = NearbyMapType.values();
@@ -37,6 +61,12 @@ public final class NearbyPlayers {
     private final ServerLevel world;
     private final Reference2ReferenceOpenHashMap<ServerPlayer, TrackedPlayer[]> players = new Reference2ReferenceOpenHashMap<>();
     private final Long2ReferenceOpenHashMap<TrackedChunk> byChunk = new Long2ReferenceOpenHashMap<>();
+    private final Long2ReferenceOpenHashMap<ReferenceList<ServerPlayer>>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES];
+    {
+        for (int i = 0; i < this.directByChunk.length; ++i) {
+            this.directByChunk[i] = new Long2ReferenceOpenHashMap<>();
+        }
+    }
 
     public NearbyPlayers(final ServerLevel world) {
         this.world = world;
@@ -70,6 +100,16 @@ public final class NearbyPlayers {
         }
     }
 
+    public void clear() {
+        if (this.players.isEmpty()) {
+            return;
+        }
+
+        for (final ServerPlayer player : new ArrayList<>(this.players.keySet())) {
+            this.removePlayer(player);
+        }
+    }
+
     public void tickPlayer(final ServerPlayer player) {
         final TrackedPlayer[] players = this.players.get(player);
         if (players == null) {
@@ -94,38 +134,41 @@ public final class NearbyPlayers {
         return this.byChunk.get(CoordinateUtils.getChunkKey(pos));
     }
 
-    public ReferenceList<ServerPlayer> getPlayers(final BlockPos pos, final NearbyMapType type) {
-        final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));
+    public TrackedChunk getChunk(final int chunkX, final int chunkZ) {
+        return this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
+    }
 
-        return chunk == null ? null : chunk.players[type.ordinal()];
+    public ReferenceList<ServerPlayer> getPlayers(final BlockPos pos, final NearbyMapType type) {
+        return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos));
     }
 
     public ReferenceList<ServerPlayer> getPlayers(final ChunkPos pos, final NearbyMapType type) {
-        final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));
-
-        return chunk == null ? null : chunk.players[type.ordinal()];
+        return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos));
     }
 
     public ReferenceList<ServerPlayer> getPlayersByChunk(final int chunkX, final int chunkZ, final NearbyMapType type) {
-        final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
-
-        return chunk == null ? null : chunk.players[type.ordinal()];
+        return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
     }
 
     public ReferenceList<ServerPlayer> getPlayersByBlock(final int blockX, final int blockZ, final NearbyMapType type) {
-        final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4));
-
-        return chunk == null ? null : chunk.players[type.ordinal()];
+        return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4));
     }
 
     public static final class TrackedChunk {
 
         private static final ServerPlayer[] EMPTY_PLAYERS_ARRAY = new ServerPlayer[0];
 
+        private final long chunkKey;
+        private final NearbyPlayers nearbyPlayers;
         private final ReferenceList<ServerPlayer>[] players = new ReferenceList[TOTAL_MAP_TYPES];
         private int nonEmptyLists;
         private long updateCount;
 
+        public TrackedChunk(final long chunkKey, final NearbyPlayers nearbyPlayers) {
+            this.chunkKey = chunkKey;
+            this.nearbyPlayers = nearbyPlayers;
+        }
+
         public boolean isEmpty() {
             return this.nonEmptyLists == 0;
         }
@@ -145,7 +188,9 @@ public final class NearbyPlayers {
             final ReferenceList<ServerPlayer> list = this.players[idx];
             if (list == null) {
                 ++this.nonEmptyLists;
-                (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)).add(player);
+                final ReferenceList<ServerPlayer> players = (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY));
+                this.nearbyPlayers.directByChunk[idx].put(this.chunkKey, players);
+                players.add(player);
                 return;
             }
 
@@ -169,6 +214,7 @@ public final class NearbyPlayers {
 
             if (list.size() == 0) {
                 this.players[idx] = null;
+                this.nearbyPlayers.directByChunk[idx].remove(this.chunkKey);
                 --this.nonEmptyLists;
             }
         }
@@ -187,9 +233,19 @@ public final class NearbyPlayers {
         protected void addCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) {
             final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
 
-            NearbyPlayers.this.byChunk.computeIfAbsent(chunkKey, (final long keyInMap) -> {
-                return new TrackedChunk();
-            }).addPlayer(parameter, this.type);
+            final TrackedChunk chunk = NearbyPlayers.this.byChunk.get(chunkKey);
+            final NearbyMapType type = this.type;
+            if (chunk != null) {
+                chunk.addPlayer(parameter, type);
+                type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ);
+            } else {
+                final TrackedChunk created = new TrackedChunk(chunkKey, NearbyPlayers.this);
+                NearbyPlayers.this.byChunk.put(chunkKey, created);
+                created.addPlayer(parameter, type);
+                type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ);
+
+                ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created;
+            }
         }
 
         @Override
@@ -201,10 +257,16 @@ public final class NearbyPlayers {
                 throw new IllegalStateException("Chunk should exist at " + new ChunkPos(chunkKey));
             }
 
-            chunk.removePlayer(parameter, this.type);
+            final NearbyMapType type = this.type;
+            chunk.removePlayer(parameter, type);
+            type.removeFrom(parameter, NearbyPlayers.this.world, chunkX, chunkZ);
 
             if (chunk.isEmpty()) {
                 NearbyPlayers.this.byChunk.remove(chunkKey);
+                final ChunkData chunkData = ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$releaseChunkData(chunkKey);
+                if (chunkData != null) {
+                    chunkData.nearbyPlayers = null;
+                }
             }
         }
     }
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
index da323a1105347d5cf4b946df10ded78a953236f2..94bba2b71918d79f54b3e28c35e76098ba0afd8c 100644
--- a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
@@ -1,6 +1,7 @@
 package ca.spottedleaf.moonrise.common.util;
 
-import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
+import ca.spottedleaf.concurrentutil.util.Priority;
+import ca.spottedleaf.moonrise.common.PlatformHooks;
 import com.mojang.logging.LogUtils;
 import net.minecraft.server.level.ChunkHolder;
 import net.minecraft.server.level.FullChunkStatus;
@@ -24,15 +25,15 @@ public final class ChunkSystem {
     }
 
     public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
-        scheduleChunkTask(level, chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL);
+        scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL);
     }
 
-    public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) {
+    public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) {
         level.chunkSource.mainThreadProcessor.execute(run);
     }
 
     public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
-                                         final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority,
+                                         final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
                                          final Consumer<ChunkAccess> onComplete) {
         if (gen) {
             scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
@@ -59,7 +60,7 @@ public final class ChunkSystem {
 
     private static long chunkLoadCounter = 0L;
     public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
-                                         final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer<ChunkAccess> onComplete) {
+                                         final boolean addTicket, final Priority priority, final Consumer<ChunkAccess> onComplete) {
         if (!org.bukkit.Bukkit.isPrimaryThread()) {
             scheduleChunkTask(level, chunkX, chunkZ, () -> {
                 scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
@@ -113,13 +114,13 @@ public final class ChunkSystem {
             }
             loadCallback.accept(result.orElse(null));
         }, (final Runnable r) -> {
-            scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST);
+            scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
         });
     }
 
     public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
                                             final FullChunkStatus toStatus, final boolean addTicket,
-                                            final PrioritisedExecutor.Priority priority, final Consumer<LevelChunk> onComplete) {
+                                            final Priority priority, final Consumer<LevelChunk> onComplete) {
         // This method goes unused until the chunk system rewrite
         if (toStatus == FullChunkStatus.INACCESSIBLE) {
             throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status");
@@ -196,7 +197,7 @@ public final class ChunkSystem {
             }
             loadCallback.accept(result.orElse(null));
         }, (final Runnable r) -> {
-            scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST);
+            scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
         });
     }
 
@@ -220,7 +221,10 @@ public final class ChunkSystem {
         return getUpdatingChunkHolderCount(level) != 0;
     }
 
-    public static boolean screenEntity(final ServerLevel level, final Entity entity) {
+    public static boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event) {
+        if (!PlatformHooks.get().screenEntity(level, entity, fromDisk, event)) {
+            return false;
+        }
         return true;
     }
 
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java
index ac6f284ee4469d16c5655328b2488d7612832353..97848869df61648fc415e4d39f409f433202c274 100644
--- a/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java
@@ -3,8 +3,12 @@ package ca.spottedleaf.moonrise.common.util;
 public final class MixinWorkarounds {
 
     // mixins tries to find the owner of the clone() method, which doesn't exist and NPEs
+    // https://github.com/FabricMC/Mixin/pull/147
     public static long[] clone(final long[] values) {
         return values.clone();
     }
 
+    public static byte[] clone(final byte[] values) {
+        return values.clone();
+    }
 }
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java
index 3abe0bd2a820352b85306d554bf14a4cf6123091..c125c70a68130be373acc989053a6c0e487be924 100644
--- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java
@@ -1,45 +1,100 @@
 package ca.spottedleaf.moonrise.common.util;
 
-import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool;
+import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool;
+import ca.spottedleaf.moonrise.common.PlatformHooks;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import java.io.File;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 
 public final class MoonriseCommon {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(MoonriseCommon.class);
 
-    // Paper start
-    public static PrioritisedThreadPool WORKER_POOL;
-    public static int WORKER_THREADS;
-    public static void init(io.papermc.paper.configuration.GlobalConfiguration.ChunkSystem chunkSystem) {
-        // Paper end
+    public static final PrioritisedThreadPool WORKER_POOL = new PrioritisedThreadPool(
+            new Consumer<>() {
+                private final AtomicInteger idGenerator = new AtomicInteger();
+
+                @Override
+                public void accept(Thread thread) {
+                    thread.setDaemon(true);
+                    thread.setName(PlatformHooks.get().getBrand() + " Common Worker #" + this.idGenerator.getAndIncrement());
+                    thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+                        @Override
+                        public void uncaughtException(final Thread thread, final Throwable throwable) {
+                            LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable);
+                        }
+                    });
+                }
+            }
+    );
+    public static final long WORKER_QUEUE_HOLD_TIME = (long)(20.0e6); // 20ms
+    public static final int CLIENT_DIVISION = 0;
+    public static final PrioritisedThreadPool.ExecutorGroup RENDER_EXECUTOR_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(CLIENT_DIVISION, 0);
+    public static final int SERVER_DIVISION = 1;
+    public static final PrioritisedThreadPool.ExecutorGroup PARALLEL_GEN_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0);
+    public static final PrioritisedThreadPool.ExecutorGroup RADIUS_AWARE_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0);
+    public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP         = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0);
+
+    public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) {
         int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2;
         if (defaultWorkerThreads <= 4) {
             defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2;
         } else {
             defaultWorkerThreads = defaultWorkerThreads / 2;
         }
-        defaultWorkerThreads = Integer.getInteger("Paper.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); // Paper
+        defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads));
 
-        int workerThreads = chunkSystem.workerThreads; // Paper
+        int workerThreads = configWorkerThreads;
 
         if (workerThreads <= 0) {
             workerThreads = defaultWorkerThreads;
         }
 
-        WORKER_POOL = new PrioritisedThreadPool(
-                "Paper Worker Pool", workerThreads, // Paper
-                (final Thread thread, final Integer id) -> {
-                    thread.setName("Paper Common Worker #" + id.intValue()); // Paper
+        final int ioThreads = Math.max(1, configIoThreads);
+
+        WORKER_POOL.adjustThreadCount(workerThreads);
+        IO_POOL.adjustThreadCount(ioThreads);
+
+        LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads");
+    }
+
+    public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool(
+            new Consumer<>() {
+                private final AtomicInteger idGenerator = new AtomicInteger();
+
+                @Override
+                public void accept(final Thread thread) {
+                    thread.setDaemon(true);
+                    thread.setName(PlatformHooks.get().getBrand() + " I/O Worker #" + this.idGenerator.getAndIncrement());
                     thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                         @Override
                         public void uncaughtException(final Thread thread, final Throwable throwable) {
                             LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable);
                         }
                     });
-                }, (long)(20.0e6)); // 20ms
-        WORKER_THREADS = workerThreads;
+                }
+            }
+    );
+    public static final long IO_QUEUE_HOLD_TIME = (long)(100.0e6); // 100ms
+    public static final PrioritisedThreadPool.ExecutorGroup CLIENT_PROFILER_IO_GROUP = IO_POOL.createExecutorGroup(CLIENT_DIVISION, 0);
+    public static final PrioritisedThreadPool.ExecutorGroup SERVER_REGION_IO_GROUP = IO_POOL.createExecutorGroup(SERVER_DIVISION, 0);
+
+    public static void haltExecutors() {
+        MoonriseCommon.WORKER_POOL.shutdown(false);
+        LOGGER.info("Awaiting termination of worker pool for up to 60s...");
+        if (!MoonriseCommon.WORKER_POOL.join(TimeUnit.SECONDS.toMillis(60L))) {
+            LOGGER.error("Worker pool did not shut down in time!");
+            MoonriseCommon.WORKER_POOL.halt(false);
+        }
+
+        MoonriseCommon.IO_POOL.shutdown(false);
+        LOGGER.info("Awaiting termination of I/O pool for up to 60s...");
+        if (!MoonriseCommon.IO_POOL.join(TimeUnit.SECONDS.toMillis(60L))) {
+            LOGGER.error("I/O pool did not shut down in time!");
+            MoonriseCommon.IO_POOL.halt(false);
+        }
     }
 
     private MoonriseCommon() {}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java
index 1cf32d7d1bbc8a0a3f7cb9024c793f6744199f64..559c959aff3c9deef867b9e425fba3e2e669cac6 100644
--- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java
@@ -1,8 +1,10 @@
 package ca.spottedleaf.moonrise.common.util;
 
+import ca.spottedleaf.moonrise.common.PlatformHooks;
+
 public final class MoonriseConstants {
 
-    public static final int MAX_VIEW_DISTANCE = 32;
+    public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32);
 
     private MoonriseConstants() {}
 
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java
new file mode 100644
index 0000000000000000000000000000000000000000..a9ff1c1a70faf4b7a64b265932f07a8b8f00c1ff
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java
@@ -0,0 +1,52 @@
+package ca.spottedleaf.moonrise.common.util;
+
+import net.minecraft.world.level.levelgen.LegacyRandomSource;
+
+/**
+ * Avoid costly CAS of superclass
+ */
+public final class SimpleRandom extends LegacyRandomSource {
+
+    private static final long MULTIPLIER = 25214903917L;
+    private static final long ADDEND = 11L;
+    private static final int BITS = 48;
+    private static final long MASK = (1L << BITS) - 1;
+
+    private long value;
+
+    public SimpleRandom(final long seed) {
+        super(0L);
+        this.value = seed;
+    }
+
+    @Override
+    public void setSeed(final long seed) {
+        this.value = (seed ^ MULTIPLIER) & MASK;
+    }
+
+    private long advanceSeed() {
+        return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK;
+    }
+
+    @Override
+    public int next(final int bits) {
+        return (int)(this.advanceSeed() >>> (BITS - bits));
+    }
+
+    @Override
+    public int nextInt() {
+        final long seed = this.advanceSeed();
+        return (int)(seed >>> (BITS - Integer.SIZE));
+    }
+
+    @Override
+    public int nextInt(final int bound) {
+        if (bound <= 0) {
+            throw new IllegalArgumentException();
+        }
+
+        // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
+        final long value = this.advanceSeed() >>> (BITS - Integer.SIZE);
+        return (int)((value * (long)bound) >>> Integer.SIZE);
+    }
+}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java
index 11b7f15755dde766140c29bedca456c80d53293f..217d1f908a36a5177ba3cbb80a33f73d4dab0fa0 100644
--- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java
@@ -77,11 +77,15 @@ public class TickThread extends Thread {
     }
 
     public TickThread(final Runnable run, final String name) {
-        this(run, name, ID_GENERATOR.incrementAndGet());
+        this(null, run, name);
     }
 
-    private TickThread(final Runnable run, final String name, final int id) {
-        super(run, name);
+    public TickThread(final ThreadGroup group, final Runnable run, final String name) {
+        this(group, run, name, ID_GENERATOR.incrementAndGet());
+    }
+
+    private TickThread(final ThreadGroup group, final Runnable run, final String name, final int id) {
+        super(group, run, name);
         this.id = id;
     }
 
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java
index af9623240ff2d389aa7090623f507720e7dbab7d..efda2688ae1254a82ba7f6bf8bf597ef224cbb86 100644
--- a/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java
@@ -8,11 +8,19 @@ public final class WorldUtil {
     // min, max are inclusive
 
     public static int getMaxSection(final LevelHeightAccessor world) {
-        return world.getMaxSection() - 1; // getMaxSection() is exclusive
+        return world.getMaxSectionY();
+    }
+
+    public static int getMaxSection(final Level world) {
+        return world.getMaxSectionY();
     }
 
     public static int getMinSection(final LevelHeightAccessor world) {
-        return world.getMinSection();
+        return world.getMinSectionY();
+    }
+
+    public static int getMinSection(final Level world) {
+        return world.getMinSectionY();
     }
 
     public static int getMaxLightSection(final LevelHeightAccessor world) {
diff --git a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
new file mode 100644
index 0000000000000000000000000000000000000000..1aa6be257ce594d7a69fdff008cd29014a04fd75
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
@@ -0,0 +1,209 @@
+package ca.spottedleaf.moonrise.paper;
+
+import ca.spottedleaf.moonrise.common.PlatformHooks;
+import com.mojang.datafixers.DSL;
+import com.mojang.datafixers.DataFixer;
+import com.mojang.serialization.Dynamic;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.NbtOps;
+import net.minecraft.server.level.ChunkHolder;
+import net.minecraft.server.level.GenerationChunkHolder;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.BlockGetter;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.ProtoChunk;
+import net.minecraft.world.level.chunk.storage.SerializableChunkData;
+import net.minecraft.world.level.entity.EntityTypeTest;
+import net.minecraft.world.phys.AABB;
+import java.util.List;
+import java.util.function.Predicate;
+
+public final class PaperHooks implements PlatformHooks {
+
+    @Override
+    public String getBrand() {
+        return "Paper";
+    }
+
+    @Override
+    public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) {
+        return blockState.getLightEmission();
+    }
+
+    @Override
+    public Predicate<BlockState> maybeHasLightEmission() {
+        return (final BlockState state) -> {
+            return state.getLightEmission() != 0;
+        };
+    }
+
+    @Override
+    public boolean hasCurrentlyLoadingChunk() {
+        return false;
+    }
+
+    @Override
+    public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) {
+        return null;
+    }
+
+    @Override
+    public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) {
+
+    }
+
+    @Override
+    public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) {
+
+    }
+
+    @Override
+    public boolean allowAsyncTicketUpdates() {
+        return true;
+    }
+
+    @Override
+    public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) {
+
+    }
+
+    @Override
+    public void chunkUnloadFromWorld(final LevelChunk chunk) {
+
+    }
+
+    @Override
+    public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) {
+
+    }
+
+    @Override
+    public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) {
+
+    }
+
+    @Override
+    public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) {
+
+    }
+
+    @Override
+    public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate<? super Entity> predicate, final List<Entity> into) {
+
+    }
+
+    @Override
+    public <T extends Entity> void addToGetEntities(final Level world, final EntityTypeTest<Entity, T> entityTypeTest, final AABB boundingBox, final Predicate<? super T> predicate, final List<? super T> into, final int maxCount) {
+
+    }
+
+    @Override
+    public void entityMove(final Entity entity, final long oldSection, final long newSection) {
+
+    }
+
+    @Override
+    public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) {
+        return true;
+    }
+
+    @Override
+    public boolean configFixMC224294() {
+        return true;
+    }
+
+    @Override
+    public boolean configAutoConfigSendDistance() {
+        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance;
+    }
+
+    @Override
+    public double configPlayerMaxLoadRate() {
+        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate;
+    }
+
+    @Override
+    public double configPlayerMaxGenRate() {
+        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate;
+    }
+
+    @Override
+    public double configPlayerMaxSendRate() {
+        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate;
+    }
+
+    @Override
+    public int configPlayerMaxConcurrentLoads() {
+        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads;
+    }
+
+    @Override
+    public int configPlayerMaxConcurrentGens() {
+        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates;
+    }
+
+    @Override
+    public long configAutoSaveInterval(final ServerLevel world) {
+        return world.paperConfig().chunks.autoSaveInterval.value();
+    }
+
+    @Override
+    public int configMaxAutoSavePerTick(final ServerLevel world) {
+        return world.paperConfig().chunks.maxAutoSaveChunksPerTick;
+    }
+
+    @Override
+    public boolean configFixMC159283() {
+        return true;
+    }
+
+    @Override
+    public boolean forceNoSave(final ChunkAccess chunk) {
+        return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave;
+    }
+
+    @Override
+    public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
+                                  final int fromVersion, final int toVersion) {
+        return (CompoundTag)dataFixer.update(
+            type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
+        ).getValue();
+    }
+
+    @Override
+    public boolean hasMainChunkLoadHook() {
+        return false;
+    }
+
+    @Override
+    public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) {
+
+    }
+
+    @Override
+    public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities) {
+        return entities;
+    }
+
+    @Override
+    public void unloadEntity(final Entity entity) {
+        entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD);
+    }
+
+    @Override
+    public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
+        net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities());
+    }
+
+    @Override
+    public int modifyEntityTrackingRange(final Entity entity, final int currentRange) {
+        return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange);
+    }
+}
diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
index 61893f8216ddaedd899b573322f3ad0088074ac5..36b96e0ed5c0d25068ec4678eddd8a19a020d345 100644
--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
@@ -242,7 +242,7 @@ public class GlobalConfiguration extends ConfigurationPart {
 
         @PostProcess
         private void postProcess() {
-
+            ca.spottedleaf.moonrise.common.util.MoonriseCommon.adjustWorkerThreads(this.workerThreads, this.ioThreads);
         }
     }
 
diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
index f18c2b85ed9541f646f157184221e333d0ae58bd..aff4c3d63a97d5bbde004a616f7e14fca59b5ab9 100644
--- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
+++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
@@ -168,7 +168,7 @@ public class ChunkStatusTasks {
         }, context.mainThreadExecutor());
     }
 
-    private static void postLoadProtoChunk(ServerLevel world, List<CompoundTag> entities) {
+    public static void postLoadProtoChunk(ServerLevel world, List<CompoundTag> entities) { // Paper - public
         if (!entities.isEmpty()) {
             // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities
             world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD).filter((entity) -> {
diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
index 6baa313b8201ed23193d7885c85606b0899ade3c..5aa74c00a61282830d82359eae2b114e2a48b6d9 100644
--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
@@ -94,15 +94,13 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
     private boolean addEntity(T entity, boolean existing) {
         org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper
         // Paper start - chunk system hooks
-        if (existing) {
-            // I don't want to know why this is a generic type.
-            Entity entityCasted = (Entity)entity;
-            boolean wasRemoved = entityCasted.isRemoved();
-            boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted);
-            if ((!wasRemoved && entityCasted.isRemoved()) || !screened) {
-                // removed by callback
-                return false;
-            }
+        // I don't want to know why this is a generic type.
+        Entity entityCasted = (Entity)entity;
+        boolean wasRemoved = entityCasted.isRemoved();
+        boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, existing, true);
+        if ((!wasRemoved && entityCasted.isRemoved()) || !screened) {
+            // removed by callback
+            return false;
         }
         // Paper end - chunk system hooks
         if (!this.addEntityUuid(entity)) {
diff --git a/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks
new file mode 100644
index 0000000000000000000000000000000000000000..e57c3ca79677b1dfe7cf3db36f0406de7ea5bd0a
--- /dev/null
+++ b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks
@@ -0,0 +1 @@
+ca.spottedleaf.moonrise.paper.PaperHooks