aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/api/0472-Registry-Modification-API.patch
blob: 6eca654483abb4de72fa8783c55f7b92e583bae3 (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
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Wed, 2 Mar 2022 13:36:21 -0800
Subject: [PATCH] Registry Modification API


diff --git a/src/main/java/io/papermc/paper/registry/RegistryBuilder.java b/src/main/java/io/papermc/paper/registry/RegistryBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..379e2638aca243bacac777cf59982f9d0e601f3e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/RegistryBuilder.java
@@ -0,0 +1,15 @@
+package io.papermc.paper.registry;
+
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * To be implemented by any type used for modifying registries.
+ *
+ * @param <T> registry value type
+ */
+@ApiStatus.Experimental
+@NullMarked
+@ApiStatus.NonExtendable
+public interface RegistryBuilder<T> {
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEvent.java b/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..56468b311e40a6d1aa03c6d31328952b92e95027
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEvent.java
@@ -0,0 +1,48 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.TypedKey;
+import io.papermc.paper.registry.tag.Tag;
+import io.papermc.paper.registry.tag.TagKey;
+import org.bukkit.Keyed;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * Event object for {@link RegistryEventProvider#entryAdd()}. This
+ * event is fired right before a specific entry is registered in/added to registry.
+ * It provides a way for plugins to modify parts of this entry.
+ *
+ * @param <T> registry entry type
+ * @param <B> registry entry builder type
+ */
+@ApiStatus.Experimental
+@NullMarked
+@ApiStatus.NonExtendable
+public interface RegistryEntryAddEvent<T, B extends RegistryBuilder<T>> extends RegistryEvent<T> {
+
+    /**
+     * Gets the builder for the entry being added to the registry.
+     *
+     * @return the object builder
+     */
+    B builder();
+
+    /**
+     * Gets the key for this entry in the registry.
+     *
+     * @return the key
+     */
+    TypedKey<T> key();
+
+    /**
+     * Gets or creates a tag for the given tag key. This tag
+     * is then required to be filled either from the built-in or
+     * custom datapack.
+     *
+     * @param tagKey the tag key
+     * @return the tag
+     * @param <V> the tag value type
+     */
+    <V extends Keyed> Tag<V> getOrCreateTag(TagKey<V> tagKey);
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEvent.java b/src/main/java/io/papermc/paper/registry/event/RegistryEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..3a8643c06d751bf3e4bff4d95b62c675ecdf0f16
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryEvent.java
@@ -0,0 +1,24 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
+import io.papermc.paper.registry.RegistryKey;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * Base type for all registry events.
+ *
+ * @param <T> registry entry type
+ */
+@ApiStatus.Experimental
+@NullMarked
+@ApiStatus.NonExtendable
+public interface RegistryEvent<T> extends LifecycleEvent {
+
+    /**
+     * Get the key for the registry this event pertains to.
+     *
+     * @return the registry key
+     */
+    RegistryKey<T> registryKey();
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventProvider.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..8518e93829318cbfe0eb70f558cb86a7b5742514
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventProvider.java
@@ -0,0 +1,57 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.plugin.bootstrap.BootstrapContext;
+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler;
+import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.RegistryKey;
+import io.papermc.paper.registry.event.type.RegistryEntryAddEventType;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * Provider for registry events for a specific registry.
+ * <p>
+ * Supported events are:
+ * <ul>
+ *     <li>{@link RegistryEntryAddEvent} (via {@link #entryAdd()})</li>
+ *     <li>{@link RegistryFreezeEvent} (via {@link #freeze()})</li>
+ * </ul>
+ *
+ * @param <T> registry entry type
+ * @param <B> registry entry builder type
+ */
+@ApiStatus.Experimental
+@NullMarked
+@ApiStatus.NonExtendable
+public interface RegistryEventProvider<T, B extends RegistryBuilder<T>> {
+
+    /**
+     * Gets the event type for {@link RegistryEntryAddEvent} which is fired just before
+     * an object is added to a registry.
+     * <p>
+     * Can be used in {@link io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager#registerEventHandler(LifecycleEventType, LifecycleEventHandler)}
+     * to register a handler for {@link RegistryEntryAddEvent}.
+     *
+     * @return the registry entry add event type
+     */
+    RegistryEntryAddEventType<T, B> entryAdd();
+
+    /**
+     * Gets the event type for {@link RegistryFreezeEvent} which is fired just before
+     * a registry is frozen. It allows for the registration of new objects.
+     * <p>
+     * Can be used in {@link io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager#registerEventHandler(LifecycleEventType, LifecycleEventHandler)}
+     * to register a handler for {@link RegistryFreezeEvent}.
+     *
+     * @return the registry freeze event type
+     */
+    LifecycleEventType.Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> freeze();
+
+    /**
+     * Gets the registry key associated with this event type provider.
+     *
+     * @return the registry key
+     */
+    RegistryKey<T> registryKey();
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventProviderImpl.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventProviderImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d9afd49077090d30f13b217b71100c73137d120
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventProviderImpl.java
@@ -0,0 +1,29 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.plugin.bootstrap.BootstrapContext;
+import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.RegistryKey;
+import io.papermc.paper.registry.event.type.RegistryEntryAddEventType;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+@ApiStatus.Internal
+@NullMarked
+record RegistryEventProviderImpl<T, B extends RegistryBuilder<T>>(RegistryKey<T> registryKey) implements RegistryEventProvider<T, B> {
+
+    static <T, B extends RegistryBuilder<T>> RegistryEventProvider<T, B> create(final RegistryKey<T> registryKey) {
+        return new RegistryEventProviderImpl<>(registryKey);
+    }
+
+    @Override
+    public RegistryEntryAddEventType<T, B> entryAdd() {
+        return RegistryEventTypeProvider.provider().registryEntryAdd(this);
+    }
+
+    @Override
+    public LifecycleEventType.Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> freeze() {
+        return RegistryEventTypeProvider.provider().registryFreeze(this);
+    }
+
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProvider.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..d807bd2f42c98e37a96cf110ad77820dfffc8398
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProvider.java
@@ -0,0 +1,24 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.plugin.bootstrap.BootstrapContext;
+import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.event.type.RegistryEntryAddEventType;
+import java.util.Optional;
+import java.util.ServiceLoader;
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Internal
+interface RegistryEventTypeProvider {
+
+    Optional<RegistryEventTypeProvider> PROVIDER = ServiceLoader.load(RegistryEventTypeProvider.class)
+        .findFirst();
+
+    static RegistryEventTypeProvider provider() {
+        return PROVIDER.orElseThrow(() -> new IllegalStateException("Could not find a %s service implementation".formatted(RegistryEventTypeProvider.class.getSimpleName())));
+    }
+
+    <T, B extends RegistryBuilder<T>> RegistryEntryAddEventType<T, B> registryEntryAdd(RegistryEventProvider<T, B> type);
+
+    <T, B extends RegistryBuilder<T>> LifecycleEventType.Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> registryFreeze(RegistryEventProvider<T, B> type);
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java b/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java
new file mode 100644
index 0000000000000000000000000000000000000000..91ae9c0d3ec55ce417d4b447bf3d1b0d0c174b5e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java
@@ -0,0 +1,16 @@
+package io.papermc.paper.registry.event;
+
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * Holds providers for {@link RegistryEntryAddEvent} and {@link RegistryFreezeEvent}
+ * handlers for each applicable registry.
+ */
+@ApiStatus.Experimental
+@NullMarked
+public final class RegistryEvents {
+
+    private RegistryEvents() {
+    }
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEvent.java b/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..59e8ca6c5b7fa0424ad9b2c74545ec53444b5fcb
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEvent.java
@@ -0,0 +1,40 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.tag.Tag;
+import io.papermc.paper.registry.tag.TagKey;
+import org.bukkit.Keyed;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * Event object for {@link RegistryEventProvider#freeze()}. This
+ * event is fired right before a registry is frozen disallowing further changes.
+ * It provides a way for plugins to add new objects to the registry.
+ *
+ * @param <T> registry entry type
+ * @param <B> registry entry builder type
+ */
+@ApiStatus.Experimental
+@NullMarked
+@ApiStatus.NonExtendable
+public interface RegistryFreezeEvent<T, B extends RegistryBuilder<T>> extends RegistryEvent<T> {
+
+    /**
+     * Get the writable registry.
+     *
+     * @return a writable registry
+     */
+    WritableRegistry<T, B> registry();
+
+    /**
+     * Gets or creates a tag for the given tag key. This tag
+     * is then required to be filled either from the built-in or
+     * custom datapack.
+     *
+     * @param tagKey the tag key
+     * @return the tag
+     * @param <V> the tag value type
+     */
+    <V extends Keyed> Tag<V> getOrCreateTag(TagKey<V> tagKey);
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/WritableRegistry.java b/src/main/java/io/papermc/paper/registry/event/WritableRegistry.java
new file mode 100644
index 0000000000000000000000000000000000000000..744f455b14cdc9131497024088da4ca0e8fc39dc
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/WritableRegistry.java
@@ -0,0 +1,28 @@
+package io.papermc.paper.registry.event;
+
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.TypedKey;
+import java.util.function.Consumer;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * A registry which supports registering new objects.
+ *
+ * @param <T> registry entry type
+ * @param <B> registry entry builder type
+ */
+@ApiStatus.Experimental
+@NullMarked
+@ApiStatus.NonExtendable
+public interface WritableRegistry<T, B extends RegistryBuilder<T>> {
+
+    /**
+     * Register a new value with the specified key. This will
+     * fire a {@link RegistryEntryAddEvent} for the new entry.
+     *
+     * @param key the entry's key (must be unique from others)
+     * @param value a consumer for the entry's builder
+     */
+    void register(TypedKey<T> key, Consumer<? super B> value);
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddConfiguration.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..d9bde790f74982c6cd5870aee35fec8a0c49fc83
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddConfiguration.java
@@ -0,0 +1,43 @@
+package io.papermc.paper.registry.event.type;
+
+import io.papermc.paper.plugin.bootstrap.BootstrapContext;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration;
+import io.papermc.paper.registry.TypedKey;
+import java.util.function.Predicate;
+import org.jetbrains.annotations.Contract;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * Specific configuration for {@link io.papermc.paper.registry.event.RegistryEntryAddEvent}s.
+ *
+ * @param <T> registry entry type
+ */
+@NullMarked
+public interface RegistryEntryAddConfiguration<T> extends PrioritizedLifecycleEventHandlerConfiguration<BootstrapContext> {
+
+    /**
+     * Only call the handler if the value being added matches the specified key.
+     *
+     * @param key the key to match
+     * @return this configuration
+     */
+    @Contract(value = "_ -> this", mutates = "this")
+    default RegistryEntryAddConfiguration<T> filter(final TypedKey<T> key) {
+        return this.filter(key::equals);
+    }
+
+    /**
+     * Only call the handler if the value being added passes the provided filter.
+     *
+     * @param filter the predicate to match the key against
+     * @return this configuration
+     */
+    @Contract(value = "_ -> this", mutates = "this")
+    RegistryEntryAddConfiguration<T> filter(Predicate<TypedKey<T>> filter);
+
+    @Override
+    RegistryEntryAddConfiguration<T> priority(int priority);
+
+    @Override
+    RegistryEntryAddConfiguration<T> monitor();
+}
diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventType.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventType.java
new file mode 100644
index 0000000000000000000000000000000000000000..93447ef58933f37fe12c4507c9a0b233be4c6c0b
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventType.java
@@ -0,0 +1,20 @@
+package io.papermc.paper.registry.event.type;
+
+import io.papermc.paper.plugin.bootstrap.BootstrapContext;
+import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
+import io.papermc.paper.registry.RegistryBuilder;
+import io.papermc.paper.registry.event.RegistryEntryAddEvent;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * Lifecycle event type for {@link RegistryEntryAddEvent}s.
+ *
+ * @param <T> registry entry type
+ * @param <B> registry entry builder type
+ */
+@ApiStatus.Experimental
+@NullMarked
+@ApiStatus.NonExtendable
+public interface RegistryEntryAddEventType<T, B extends RegistryBuilder<T>> extends LifecycleEventType<BootstrapContext, RegistryEntryAddEvent<T, B>, RegistryEntryAddConfiguration<T>> {
+}
diff --git a/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java b/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java
new file mode 100644
index 0000000000000000000000000000000000000000..d5a5e332abc861d8509a5df7b57319f72e6b5449
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java
@@ -0,0 +1,51 @@
+package io.papermc.paper.registry.set;
+
+import io.papermc.paper.registry.TypedKey;
+import java.util.Collection;
+import java.util.Iterator;
+import org.bukkit.Keyed;
+import org.bukkit.Registry;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Unmodifiable;
+import org.jspecify.annotations.NullMarked;
+
+@ApiStatus.Experimental
+@NullMarked
+@ApiStatus.NonExtendable
+public non-sealed interface RegistryKeySet<T extends Keyed> extends Iterable<TypedKey<T>>, RegistrySet<T> { // TODO remove Keyed
+
+    @Override
+    default int size() {
+        return this.values().size();
+    }
+
+    /**
+     * Get the keys for the values in this set.
+     *
+     * @return the keys
+     */
+    @Unmodifiable Collection<TypedKey<T>> values();
+
+    /**
+     * Resolve this set into a collection of values. Prefer using
+     * {@link #values()}.
+     *
+     * @param registry the registry to resolve the values from (must match {@link #registryKey()})
+     * @return the resolved values
+     * @see RegistryKeySet#values()
+     */
+    @Unmodifiable Collection<T> resolve(final Registry<T> registry);
+
+    /**
+     * Checks if this set contains the value with the given key.
+     *
+     * @param valueKey the key to check
+     * @return true if the value is in this set
+     */
+    boolean contains(TypedKey<T> valueKey);
+
+    @Override
+    default Iterator<TypedKey<T>> iterator() {
+        return this.values().iterator();
+    }
+}
diff --git a/src/main/java/io/papermc/paper/registry/set/RegistryKeySetImpl.java b/src/main/java/io/papermc/paper/registry/set/RegistryKeySetImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe4a0817d7ac4a9f958a6156d6ae3ac1c96f58cd
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/set/RegistryKeySetImpl.java
@@ -0,0 +1,52 @@
+package io.papermc.paper.registry.set;
+
+import com.google.common.base.Preconditions;
+import io.papermc.paper.registry.RegistryAccess;
+import io.papermc.paper.registry.RegistryKey;
+import io.papermc.paper.registry.TypedKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.bukkit.Keyed;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Registry;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+@ApiStatus.Internal
+@NullMarked
+record RegistryKeySetImpl<T extends @Nullable Keyed>(RegistryKey<T> registryKey, List<TypedKey<T>> values) implements RegistryKeySet<T> { // TODO remove Keyed
+
+    static <T extends Keyed> RegistryKeySet<T> create(final RegistryKey<T> registryKey, final Iterable<? extends T> values) { // TODO remove Keyed
+        final Registry<T> registry = RegistryAccess.registryAccess().getRegistry(registryKey);
+        final ArrayList<TypedKey<T>> keys = new ArrayList<>();
+        for (final T value : values) {
+            final NamespacedKey key = registry.getKey(value);
+            Preconditions.checkArgument(key != null, value + " does not have a key in " + registryKey);
+            keys.add(TypedKey.create(registryKey, key));
+        }
+        return new RegistryKeySetImpl<>(registryKey, keys);
+    }
+
+    RegistryKeySetImpl {
+        values = List.copyOf(values);
+    }
+
+    @Override
+    public boolean contains(final TypedKey<T> valueKey) {
+        return this.values.contains(valueKey);
+    }
+
+    @Override
+    public Collection<T> resolve(final Registry<T> registry) {
+        final List<T> values = new ArrayList<>(this.values.size());
+        for (final TypedKey<T> key : this.values) {
+            final T value = registry.get(key.key());
+            Preconditions.checkState(value != null, "Trying to access unbound TypedKey: " + key);
+            values.add(value);
+        }
+        return Collections.unmodifiableList(values);
+    }
+}
diff --git a/src/main/java/io/papermc/paper/registry/set/RegistrySet.java b/src/main/java/io/papermc/paper/registry/set/RegistrySet.java
new file mode 100644
index 0000000000000000000000000000000000000000..c6fdeb05242418d20472aeed8cb61bfe937911c8
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/set/RegistrySet.java
@@ -0,0 +1,113 @@
+package io.papermc.paper.registry.set;
+
+import com.google.common.collect.Lists;
+import io.papermc.paper.registry.RegistryKey;
+import io.papermc.paper.registry.TypedKey;
+import io.papermc.paper.registry.tag.Tag;
+import org.bukkit.Keyed;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Contract;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * Represents a collection tied to a registry.
+ * <p>
+ * There are 2<!--3--> types of registry sets:
+ * <ul>
+ *     <li>{@link Tag} which is a tag from vanilla or a datapack.
+ *     These are obtained via {@link org.bukkit.Registry#getTag(io.papermc.paper.registry.tag.TagKey)}.</li>
+ *     <li>{@link RegistryKeySet} which is a set of of keys linked to values that are present in the registry. These are
+ *     created via {@link #keySet(RegistryKey, Iterable)} or {@link #keySetFromValues(RegistryKey, Iterable)}.</li>
+ *     <!-- <li>{@link RegistryValueSet} which is a set of values which are anonymous (don't have keys in the registry). These are
+ *     created via {@link #valueSet(RegistryKey, Iterable)}.</li>-->
+ * </ul>
+ *
+ * @param <T> registry value type
+ */
+@ApiStatus.Experimental
+@NullMarked
+public sealed interface RegistrySet<T> permits RegistryKeySet, RegistryValueSet {
+
+    // TODO uncomment when direct holder sets need to be exposed to the API
+    // /**
+    //  * Creates a {@link RegistryValueSet} from anonymous values.
+    //  * <p>All values provided <b>must not</b> have keys in the given registry.</p>
+    //  *
+    //  * @param registryKey the registry key for the type of these values
+    //  * @param values the values
+    //  * @return a new registry set
+    //  * @param <T> the type of the values
+    //  */
+    // @Contract(value = "_, _ -> new", pure = true)
+    // static <T> RegistryValueSet<T> valueSet(final RegistryKey<T> registryKey, final Iterable<? extends T> values) {
+    //     return RegistryValueSetImpl.create(registryKey, values);
+    // }
+
+    /**
+     * Creates a {@link RegistryKeySet} from registry-backed values.
+     * <p>All values provided <b>must</b> have keys in the given registry.
+     * <!--For anonymous values, use {@link #valueSet(RegistryKey, Iterable)}--></p>
+     * <p>If references to actual objects are not available yet, use {@link #keySet(RegistryKey, Iterable)} to
+     * create an equivalent {@link RegistryKeySet} using just {@link TypedKey TypedKeys}.</p>
+     *
+     * @param registryKey the registry key for the owner of these values
+     * @param values the values
+     * @return a new registry set
+     * @param <T> the type of the values
+     * @throws IllegalArgumentException if the registry isn't available yet or if any value doesn't have a key in that registry
+     */
+    @Contract(value = "_, _ -> new", pure = true)
+    static <T extends Keyed> RegistryKeySet<T> keySetFromValues(final RegistryKey<T> registryKey, final Iterable<? extends T> values) { // TODO remove Keyed
+        return RegistryKeySetImpl.create(registryKey, values);
+    }
+
+    /**
+     * Creates a direct {@link RegistrySet} from {@link TypedKey TypedKeys}.
+     *
+     * @param registryKey the registry key for the owner of these keys
+     * @param keys the keys for the values
+     * @return a new registry set
+     * @param <T> the type of the values
+     */
+    @SafeVarargs
+    static <T extends Keyed> RegistryKeySet<T> keySet(final RegistryKey<T> registryKey, final TypedKey<T>... keys) { // TODO remove Keyed
+        return keySet(registryKey, Lists.newArrayList(keys));
+    }
+
+    /**
+     * Creates a direct {@link RegistrySet} from {@link TypedKey TypedKeys}.
+     *
+     * @param registryKey the registry key for the owner of these keys
+     * @param keys the keys for the values
+     * @return a new registry set
+     * @param <T> the type of the values
+     */
+    @SuppressWarnings("BoundedWildcard")
+    @Contract(value = "_, _ -> new", pure = true)
+    static <T extends Keyed> RegistryKeySet<T> keySet(final RegistryKey<T> registryKey, final Iterable<TypedKey<T>> keys) { // TODO remove Keyed
+        return new RegistryKeySetImpl<>(registryKey, Lists.newArrayList(keys));
+    }
+
+    /**
+     * Get the registry key for this set.
+     *
+     * @return the registry key
+     */
+    RegistryKey<T> registryKey();
+
+    /**
+     * Get the size of this set.
+     *
+     * @return the size
+     */
+    int size();
+
+    /**
+     * Checks if the registry set is empty.
+     *
+     * @return true, if empty
+     */
+    default boolean isEmpty() {
+        return this.size() == 0;
+    }
+}
diff --git a/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java b/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f7761535c30c376dad64678fa2ee24bbb041e06
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java
@@ -0,0 +1,35 @@
+package io.papermc.paper.registry.set;
+
+import java.util.Collection;
+import java.util.Iterator;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Unmodifiable;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * A collection of anonymous values relating to a registry. These
+ * are values of the same type as the registry, but will not be found
+ * in the registry, hence, anonymous.
+ * @param <T> registry value type
+ */
+@ApiStatus.Experimental
+@NullMarked
+public sealed interface RegistryValueSet<T> extends Iterable<T>, RegistrySet<T> permits RegistryValueSetImpl {
+
+    @Override
+    default int size() {
+        return this.values().size();
+    }
+
+    /**
+     * Get the collection of values in this direct set.
+     *
+     * @return the values
+     */
+    @Unmodifiable Collection<T> values();
+
+    @Override
+    default Iterator<T> iterator() {
+        return this.values().iterator();
+    }
+}
diff --git a/src/main/java/io/papermc/paper/registry/set/RegistryValueSetImpl.java b/src/main/java/io/papermc/paper/registry/set/RegistryValueSetImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..1bcc6b4b9ff0090570d4f39a49e10e1fa03edb7d
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/set/RegistryValueSetImpl.java
@@ -0,0 +1,20 @@
+package io.papermc.paper.registry.set;
+
+import com.google.common.collect.Lists;
+import io.papermc.paper.registry.RegistryKey;
+import java.util.List;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+@ApiStatus.Internal
+@NullMarked
+record RegistryValueSetImpl<T>(RegistryKey<T> registryKey, List<T> values) implements RegistryValueSet<T> {
+
+    RegistryValueSetImpl {
+        values = List.copyOf(values);
+    }
+
+    static <T> RegistryValueSet<T> create(final RegistryKey<T> registryKey, final Iterable<? extends T> values) {
+        return new RegistryValueSetImpl<>(registryKey, Lists.newArrayList(values));
+    }
+}
diff --git a/src/main/java/io/papermc/paper/registry/tag/Tag.java b/src/main/java/io/papermc/paper/registry/tag/Tag.java
new file mode 100644
index 0000000000000000000000000000000000000000..245eb1074ce2930e4d9ff42a5df49004d08bbac2
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/tag/Tag.java
@@ -0,0 +1,26 @@
+package io.papermc.paper.registry.tag;
+
+import io.papermc.paper.registry.set.RegistryKeySet;
+import org.bukkit.Keyed;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * A named {@link RegistryKeySet} which are created
+ * via the datapack tag system.
+ *
+ * @param <T>
+ * @see org.bukkit.Tag
+ * @see org.bukkit.Registry#getTag(TagKey)
+ */
+@ApiStatus.Experimental
+@NullMarked
+public interface Tag<T extends Keyed> extends RegistryKeySet<T> { // TODO remove Keyed
+
+    /**
+     * Get the identifier for this named set.
+     *
+     * @return the tag key identifier
+     */
+    TagKey<T> tagKey();
+}
diff --git a/src/main/java/io/papermc/paper/registry/tag/TagKey.java b/src/main/java/io/papermc/paper/registry/tag/TagKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..f84d95b29fd2c0a52d2769fa9d3e2326ebc8aa3f
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/tag/TagKey.java
@@ -0,0 +1,33 @@
+package io.papermc.paper.registry.tag;
+
+import io.papermc.paper.registry.RegistryKey;
+import net.kyori.adventure.key.Key;
+import net.kyori.adventure.key.Keyed;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Contract;
+import org.jspecify.annotations.NullMarked;
+
+@ApiStatus.Experimental
+@NullMarked
+public sealed interface TagKey<T> extends Keyed permits TagKeyImpl {
+
+    /**
+     * Creates a new tag key for a registry.
+     *
+     * @param registryKey the registry for the tag
+     * @param key the specific key for the tag
+     * @return a new tag key
+     * @param <T> the registry value type
+     */
+    @Contract(value = "_, _ -> new", pure = true)
+    static <T> TagKey<T> create(final RegistryKey<T> registryKey, final Key key) {
+        return new TagKeyImpl<>(registryKey, key);
+    }
+
+    /**
+     * Get the registry key for this tag key.
+     *
+     * @return the registry key
+     */
+    RegistryKey<T> registryKey();
+}
diff --git a/src/main/java/io/papermc/paper/registry/tag/TagKeyImpl.java b/src/main/java/io/papermc/paper/registry/tag/TagKeyImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf49125acc8a0508bf59674bba3ed3505ee9481a
--- /dev/null
+++ b/src/main/java/io/papermc/paper/registry/tag/TagKeyImpl.java
@@ -0,0 +1,16 @@
+package io.papermc.paper.registry.tag;
+
+import io.papermc.paper.registry.RegistryKey;
+import net.kyori.adventure.key.Key;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+@ApiStatus.Internal
+@NullMarked
+record TagKeyImpl<T>(RegistryKey<T> registryKey, Key key) implements TagKey<T> {
+
+    @Override
+    public String toString() {
+        return "#" + this.key + " (in " + this.registryKey + ")";
+    }
+}
diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java
index 67cf3fcad21a8977d6fad172cc776b628ab68f25..b4ef3133fdd9d79a3381cf8f659ff561ab2b4fad 100644
--- a/src/main/java/org/bukkit/Registry.java
+++ b/src/main/java/org/bukkit/Registry.java
@@ -378,6 +378,27 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
      */
     @Nullable
     T get(@NotNull NamespacedKey key);
+    // Paper start
+    /**
+     * Get the object by its key.
+     *
+     * @param key non-null key
+     * @return item or null if it does not exist
+     */
+    default @Nullable T get(final net.kyori.adventure.key.@NotNull Key key) {
+        return key instanceof final NamespacedKey nsKey ? this.get(nsKey) : this.get(new NamespacedKey(key.namespace(), key.value()));
+    }
+
+    /**
+     * Get the object by its typed key.
+     *
+     * @param typedKey non-null typed key
+     * @return item or null if it does not exist
+     */
+    default @Nullable T get(final io.papermc.paper.registry.@NotNull TypedKey<T> typedKey) {
+        return this.get(typedKey.key());
+    }
+    // Paper end
 
     // Paper start - improve Registry
     /**
@@ -452,6 +473,34 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
     }
     // Paper end - improve Registry
 
+    // Paper start - RegistrySet API
+    /**
+     * Checks if this registry has a tag with the given key.
+     *
+     * @param key the key to check for
+     * @return true if this registry has a tag with the given key, false otherwise
+     * @see #getTag(io.papermc.paper.registry.tag.TagKey)
+     */
+    @ApiStatus.Experimental
+    default boolean hasTag(final io.papermc.paper.registry.tag.@NotNull TagKey<T> key) {
+        throw new UnsupportedOperationException(this + " doesn't have tags");
+    }
+
+    /**
+     * Gets the named registry set (tag) for the given key.
+     *
+     * @param key the key to get the tag for
+     * @return the tag for the key
+     * @throws java.util.NoSuchElementException if no tag with the given key is found
+     * @throws UnsupportedOperationException if this registry doesn't have or support tags
+     * @see #hasTag(io.papermc.paper.registry.tag.TagKey)
+     */
+    @ApiStatus.Experimental
+    default @NotNull io.papermc.paper.registry.tag.Tag<T> getTag(final io.papermc.paper.registry.tag.@NotNull TagKey<T> key) {
+        throw new UnsupportedOperationException(this + " doesn't have tags");
+    }
+    // Paper end - RegistrySet API
+
     /**
      * Get the object by its key.
      *
@@ -555,5 +604,23 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
             return value.getKey();
         }
         // Paper end - improve Registry
+
+        // Paper start - RegistrySet API
+        @SuppressWarnings("deprecation")
+        @Override
+        public boolean hasTag(final io.papermc.paper.registry.tag.@NotNull TagKey<T> key) {
+            return Bukkit.getUnsafe().getTag(key) != null;
+        }
+
+        @SuppressWarnings("deprecation")
+        @Override
+        public io.papermc.paper.registry.tag.@NotNull Tag<T> getTag(final io.papermc.paper.registry.tag.@NotNull TagKey<T> key) {
+            final io.papermc.paper.registry.tag.Tag<T> tag = Bukkit.getUnsafe().getTag(key);
+            if (tag == null) {
+                throw new java.util.NoSuchElementException("No tag " + key + " found");
+            }
+            return tag;
+        }
+        // Paper end - RegistrySet API
     }
 }
diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java
index 31217b38e769f97801fa1afefeb223d1c755cabd..9bdba60fa96edbc4be5dcf54a815579db887048b 100644
--- a/src/main/java/org/bukkit/UnsafeValues.java
+++ b/src/main/java/org/bukkit/UnsafeValues.java
@@ -283,4 +283,6 @@ public interface UnsafeValues {
     // Paper end - lifecycle event API
 
     @NotNull java.util.List<net.kyori.adventure.text.Component> computeTooltipLines(@NotNull ItemStack itemStack, @NotNull io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, @Nullable org.bukkit.entity.Player player); // Paper - expose itemstack tooltip lines
+
+    <A extends Keyed, M> io.papermc.paper.registry.tag.@Nullable Tag<A> getTag(io.papermc.paper.registry.tag.@NotNull TagKey<A> tagKey); // Paper - hack to get tags for non-server backed registries
 }