Skip to content

Commit 1800c7a

Browse files
committed
More work on bottomless bundles and enchantments
1 parent 38a4f80 commit 1800c7a

15 files changed

Lines changed: 176 additions & 235 deletions

File tree

src/main/java/de/dafuqs/spectrum/api/block/ImplementedInventory.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,18 @@ static ImplementedInventory of(DefaultedList<ItemStack> items) {
3333
static ImplementedInventory ofSize(int size) {
3434
return of(DefaultedList.ofSize(size, ItemStack.EMPTY));
3535
}
36-
36+
3737
/**
3838
* Returns the inventory size.
3939
*/
4040
@Override
4141
default int size() {
4242
return getItems().size();
4343
}
44+
@Override
45+
default int getSize() {
46+
return size();
47+
}
4448

4549
/**
4650
* Checks if the inventory is empty.
@@ -65,6 +69,10 @@ default boolean isEmpty() {
6569
default ItemStack getStack(int slot) {
6670
return getItems().get(slot);
6771
}
72+
@Override
73+
default ItemStack getStackInSlot(int slot) {
74+
return getStack(slot);
75+
}
6876

6977
/**
7078
* Removes items from an inventory slot.

src/main/java/de/dafuqs/spectrum/api/item/ExtendedEnchantable.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44

55
/**
66
* Items with that Interface will be able to be enchanted with the given enchants
7-
* Most notably for items that do not match an existing net.minecraft.enchantment.EnchantmentTarget
8-
* (since that one is an enum, it cannot be easily extended)
9-
* <p>
10-
* Using this interface there is no need to mixin into every individual enchantment to override `isAcceptableItem`
7+
* Using this interface there is no need to override every vanilla enchantment to override `supported_items`
118
* Note that for an item to be enchanted, it still needs to have `isEnchantable() == true` and `getEnchantability() > 0`
129
*/
1310
public interface ExtendedEnchantable {

src/main/java/de/dafuqs/spectrum/blocks/bottomless_bundle/BottomlessBundleBlock.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public void onPlaced(World world, BlockPos pos, BlockState state, LivingEntity p
151151
if (!world.isClient) {
152152
BlockEntity blockEntity = world.getBlockEntity(pos);
153153
if (blockEntity instanceof BottomlessBundleBlockEntity bottomlessBundleBlockEntity) {
154-
bottomlessBundleBlockEntity.setBundle(itemStack.copy());
154+
bottomlessBundleBlockEntity.setBundle(itemStack.copy(), world.getRegistryManager());
155155
world.updateComparators(pos, this);
156156
}
157157
}

src/main/java/de/dafuqs/spectrum/blocks/bottomless_bundle/BottomlessBundleBlockEntity.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package de.dafuqs.spectrum.blocks.bottomless_bundle;
22

3-
import de.dafuqs.spectrum.helpers.SpectrumEnchantmentHelper;
43
import de.dafuqs.spectrum.registries.*;
54
import net.fabricmc.fabric.api.transfer.v1.item.*;
65
import net.fabricmc.fabric.api.transfer.v1.storage.base.*;
@@ -10,6 +9,7 @@
109
import net.minecraft.enchantment.*;
1110
import net.minecraft.item.*;
1211
import net.minecraft.nbt.*;
12+
import net.minecraft.registry.RegistryKeys;
1313
import net.minecraft.registry.RegistryWrapper;
1414
import net.minecraft.util.math.*;
1515
import org.jetbrains.annotations.*;
@@ -67,7 +67,7 @@ public BottomlessBundleBlockEntity(BlockPos pos, BlockState state) {
6767
@Override
6868
public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
6969
super.readNbt(nbt, registryLookup);
70-
this.setBundleUnsynced(ItemStack.fromNbt(nbt.getCompound("Bundle")));
70+
this.setBundleUnsynced(ItemStack.fromNbt(registryLookup, nbt.getCompound("Bundle")).orElse(SpectrumItems.BOTTOMLESS_BUNDLE.getDefaultStack()), registryLookup);
7171

7272
// Handle old data by syncing into bundle
7373
if (nbt.contains("StorageVariant")) {
@@ -103,19 +103,19 @@ protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryL
103103
nbt.put("Bundle", bundleCompound);
104104
}
105105

106-
private boolean setBundleUnsynced(ItemStack itemStack) {
106+
private boolean setBundleUnsynced(ItemStack itemStack, RegistryWrapper.WrapperLookup registryLookup) {
107107
if (itemStack.getItem() instanceof BottomlessBundleItem) {
108108
this.bottomlessBundleStack = itemStack;
109109
// cache once, use many times
110110
this.isVoiding = EnchantmentHelper.hasAnyEnchantmentsIn(bottomlessBundleStack, SpectrumEnchantmentTags.DELETES_OVERFLOW);
111-
this.powerLevel = EnchantmentHelper.getLevel(Enchantments.POWER, itemStack);
111+
this.powerLevel = EnchantmentHelper.getLevel(registryLookup.getOptionalWrapper(RegistryKeys.ENCHANTMENT).flatMap(impl -> impl.getOptional(Enchantments.POWER)).orElse(null), itemStack);
112112
return true;
113113
}
114114
return false;
115115
}
116116

117-
public void setBundle(@NotNull ItemStack itemStack) {
118-
if (setBundleUnsynced(itemStack)) syncStorageWithBundle();
117+
public void setBundle(@NotNull ItemStack itemStack, RegistryWrapper.WrapperLookup registryLookup) {
118+
if (setBundleUnsynced(itemStack, registryLookup)) syncStorageWithBundle();
119119
}
120120

121121
public ItemStack retrieveBundle() {

src/main/java/de/dafuqs/spectrum/blocks/bottomless_bundle/BottomlessBundleItem.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import net.minecraft.client.render.model.*;
1717
import net.minecraft.client.render.model.json.*;
1818
import net.minecraft.client.util.math.*;
19+
import net.minecraft.component.DataComponentTypes;
1920
import net.minecraft.enchantment.*;
2021
import net.minecraft.entity.*;
2122
import net.minecraft.entity.player.*;
@@ -115,11 +116,7 @@ private static boolean dropOneBundledStack(ItemStack voidBundleStack, PlayerEnti
115116
}
116117

117118
public static boolean isLocked(ItemStack itemStack) {
118-
NbtCompound compound = itemStack.getNbt();
119-
if (compound != null) {
120-
return compound.getBoolean("Locked");
121-
}
122-
return false;
119+
return itemStack.contains(DataComponentTypes.LOCK);
123120
}
124121

125122
public static ItemStack getFirstBundledStack(ItemStack voidBundleStack) {
@@ -229,14 +226,13 @@ public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand han
229226
ItemStack itemStack = user.getStackInHand(hand);
230227
if (user.isSneaking()) {
231228
ItemStack handStack = user.getStackInHand(hand);
232-
NbtCompound compound = handStack.getOrCreateNbt();
233-
if (compound.contains("Locked")) {
234-
compound.remove("Locked");
229+
if (handStack.contains(DataComponentTypes.LOCK)) {
230+
handStack.remove(DataComponentTypes.LOCK);
235231
if (world.isClient) {
236232
playZipSound(user, 0.8F);
237233
}
238234
} else {
239-
compound.putBoolean("Locked", true);
235+
handStack.set(DataComponentTypes.LOCK, ContainerLock.EMPTY);
240236
if (world.isClient) {
241237
playZipSound(user, 1.0F);
242238
}

src/main/java/de/dafuqs/spectrum/blocks/enchanter/EnchanterBlockEntity.java

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import de.dafuqs.spectrum.blocks.item_bowl.*;
99
import de.dafuqs.spectrum.blocks.upgrade.*;
1010
import de.dafuqs.spectrum.compat.biome_makeover.*;
11-
import de.dafuqs.spectrum.enchantments.*;
1211
import de.dafuqs.spectrum.helpers.*;
1312
import de.dafuqs.spectrum.items.magic_items.*;
1413
import de.dafuqs.spectrum.networking.*;
@@ -23,6 +22,7 @@
2322
import net.minecraft.advancement.criterion.*;
2423
import net.minecraft.block.*;
2524
import net.minecraft.block.entity.*;
25+
import net.minecraft.component.type.ItemEnchantmentsComponent;
2626
import net.minecraft.enchantment.*;
2727
import net.minecraft.entity.player.*;
2828
import net.minecraft.inventory.*;
@@ -31,6 +31,8 @@
3131
import net.minecraft.network.listener.*;
3232
import net.minecraft.network.packet.*;
3333
import net.minecraft.network.packet.s2c.play.*;
34+
import net.minecraft.registry.entry.RegistryEntry;
35+
import net.minecraft.registry.tag.EnchantmentTags;
3436
import net.minecraft.server.network.*;
3537
import net.minecraft.server.world.*;
3638
import net.minecraft.sound.*;
@@ -65,14 +67,14 @@ public class EnchanterBlockEntity extends InWorldInteractionBlockEntity implemen
6567
// since the item bowls around the enchanter hold some items themselves
6668
// they get cached here for faster recipe lookup
6769
// virtualInventoryRecipeOrientation is the order the items are ordered for the recipe to match (rotations from 0-3)
68-
protected SimpleInventory virtualInventoryIncludingBowlStacks;
70+
protected ImplementedInventory virtualInventoryIncludingBowlStacks;
6971
protected int virtualInventoryRecipeOrientation;
7072
protected boolean virtualInventoryRecipeMirrored;
7173

7274
protected boolean inventoryChanged;
7375
private UpgradeHolder upgrades;
7476

75-
private GatedSpectrumRecipe currentRecipe;
77+
private GatedSpectrumRecipe<?> currentRecipe;
7678
private int craftingTime;
7779
private int craftingTimeTotal;
7880
private int currentItemProcessingTime;
@@ -82,7 +84,7 @@ public class EnchanterBlockEntity extends InWorldInteractionBlockEntity implemen
8284

8385
public EnchanterBlockEntity(BlockPos pos, BlockState state) {
8486
super(SpectrumBlockEntities.ENCHANTER, pos, state, INVENTORY_SIZE);
85-
this.virtualInventoryIncludingBowlStacks = new SimpleInventory(INVENTORY_SIZE + 8);
87+
this.virtualInventoryIncludingBowlStacks = ImplementedInventory.ofSize(INVENTORY_SIZE + 8);
8688
this.currentItemProcessingTime = -1;
8789
}
8890

@@ -230,23 +232,31 @@ public static void serverTick(World world, BlockPos blockPos, BlockState blockSt
230232
public static boolean isValidCenterEnchantingSetup(@NotNull EnchanterBlockEntity enchanterBlockEntity) {
231233
ItemStack centerStack = enchanterBlockEntity.virtualInventoryIncludingBowlStacks.getStack(0);
232234
boolean isEnchantableBookInCenter = SpectrumEnchantmentHelper.isEnchantableBook(centerStack);
233-
if (!centerStack.isEmpty() && (isEnchantableBookInCenter || centerStack.getItem().isEnchantable(centerStack)) && enchanterBlockEntity.virtualInventoryIncludingBowlStacks.getStack(1).getItem() instanceof ExperienceStorageItem) {
235+
236+
var centerIsEnchantable = (isEnchantableBookInCenter || centerStack.getItem().isEnchantable(centerStack));
237+
var hasExpStorage = enchanterBlockEntity.virtualInventoryIncludingBowlStacks.getStack(1).getItem() instanceof ExperienceStorageItem;
238+
239+
if (!centerStack.isEmpty() && centerIsEnchantable && hasExpStorage) {
234240
// gilded books can copy enchantments from any source item
235241
boolean centerStackIsGildedBook = centerStack.isOf(SpectrumItems.GILDED_BOOK);
236242
boolean enchantedBookWithAdditionalEnchantmentsFound = false;
237-
Map<Enchantment, Integer> existingEnchantments = EnchantmentHelper.get(centerStack);
243+
244+
var existingEnchantments = EnchantmentHelper.getEnchantments(centerStack).getEnchantmentEntries();
238245
for (int i = 0; i < 8; i++) {
239246
ItemStack virtualSlotStack = enchanterBlockEntity.virtualInventoryIncludingBowlStacks.getStack(2 + i);
247+
240248
// empty slots do not count
241249
if (!virtualSlotStack.isEmpty()) {
242250
if (centerStackIsGildedBook || virtualSlotStack.getItem() instanceof EnchantedBookItem) {
243-
Map<Enchantment, Integer> currentEnchantedBookEnchantments = EnchantmentHelper.get(virtualSlotStack);
244-
for (Enchantment enchantment : currentEnchantedBookEnchantments.keySet()) {
245-
if ((isEnchantableBookInCenter || enchantment.isAcceptableItem(centerStack)) && (!existingEnchantments.containsKey(enchantment) || existingEnchantments.get(enchantment) < currentEnchantedBookEnchantments.get(enchantment))) {
251+
for (var entry : EnchantmentHelper.getEnchantments(virtualSlotStack).getEnchantmentEntries()) {
252+
var enchantment = entry.getKey();
253+
var isAcceptable = isEnchantableBookInCenter || enchantment.value().isAcceptableItem(centerStack);
254+
var isRedundant = existingEnchantments.stream().anyMatch(existing -> existing.getKey() == enchantment && existing.getIntValue() >= entry.getIntValue());
255+
if (isAcceptable && !isRedundant) {
246256
if (enchanterBlockEntity.canOwnerApplyConflictingEnchantments) {
247257
enchantedBookWithAdditionalEnchantmentsFound = true;
248258
break;
249-
} else if (SpectrumEnchantmentHelper.canCombineAny(existingEnchantments, currentEnchantedBookEnchantments)) {
259+
} else if (SpectrumEnchantmentHelper.canCombineAny(centerStack, virtualSlotStack)) {
250260
enchantedBookWithAdditionalEnchantmentsFound = true;
251261
break;
252262
}
@@ -257,8 +267,10 @@ public static boolean isValidCenterEnchantingSetup(@NotNull EnchanterBlockEntity
257267
}
258268
}
259269
}
270+
260271
return enchantedBookWithAdditionalEnchantmentsFound;
261272
}
273+
262274
return false;
263275
}
264276

@@ -300,10 +312,10 @@ private static boolean checkRecipeRequirements(World world, BlockPos blockPos, @
300312
public static void enchantCenterItem(@NotNull EnchanterBlockEntity enchanterBlockEntity) {
301313
ItemStack centerStack = enchanterBlockEntity.getStack(0);
302314
ItemStack centerStackCopy = centerStack.copy();
303-
Map<Enchantment, Integer> highestEnchantments = getHighestEnchantmentsInItemBowls(enchanterBlockEntity);
315+
var highestEnchantments = getHighestEnchantmentsInItemBowls(enchanterBlockEntity);
304316

305-
for (Enchantment enchantment : highestEnchantments.keySet()) {
306-
centerStackCopy = SpectrumEnchantmentHelper.addOrUpgradeEnchantment(centerStackCopy, enchantment, highestEnchantments.get(enchantment), false, enchanterBlockEntity.canOwnerApplyConflictingEnchantments).getRight();
317+
for (var entry : highestEnchantments.getEnchantmentEntries()) {
318+
centerStackCopy = SpectrumEnchantmentHelper.addOrUpgradeEnchantment(centerStackCopy, entry.getKey(), entry.getIntValue(), false, enchanterBlockEntity.canOwnerApplyConflictingEnchantments).getRight();
307319
}
308320

309321
int spentExperience = enchanterBlockEntity.currentItemProcessingTime / EnchanterBlockEntity.REQUIRED_TICKS_FOR_EACH_EXPERIENCE_POINT;
@@ -325,27 +337,24 @@ public static void enchantCenterItem(@NotNull EnchanterBlockEntity enchanterBloc
325337
}
326338
}
327339

328-
public static Map<Enchantment, Integer> getHighestEnchantmentsInItemBowls(@NotNull EnchanterBlockEntity enchanterBlockEntity) {
329-
List<ItemStack> bowlStacks = new ArrayList<>();
330-
for (int i = 0; i < 8; i++) {
331-
bowlStacks.add(enchanterBlockEntity.virtualInventoryIncludingBowlStacks.getStack(2 + i));
332-
}
333-
334-
return SpectrumEnchantmentHelper.collectHighestEnchantments(bowlStacks);
340+
public static ItemEnchantmentsComponent getHighestEnchantmentsInItemBowls(@NotNull EnchanterBlockEntity enchanterBlockEntity) {
341+
return SpectrumEnchantmentHelper.collectHighestEnchantments(
342+
enchanterBlockEntity.virtualInventoryIncludingBowlStacks.getItems().subList(2, 10));
335343
}
336344

337345
public static int getRequiredExperienceToEnchantCenterItem(@NotNull EnchanterBlockEntity enchanterBlockEntity) {
338346
boolean valid = false;
339347
ItemStack centerStack = enchanterBlockEntity.getStack(0);
340348
if (!centerStack.isEmpty() && (centerStack.getItem().isEnchantable(centerStack) || SpectrumEnchantmentHelper.isEnchantableBook(centerStack))) {
341349
ItemStack centerStackCopy = centerStack.copy();
342-
Map<Enchantment, Integer> highestEnchantmentLevels = getHighestEnchantmentsInItemBowls(enchanterBlockEntity);
350+
var highestEnchantments = getHighestEnchantmentsInItemBowls(enchanterBlockEntity);
343351
int requiredExperience = 0;
344-
for (Enchantment enchantment : highestEnchantmentLevels.keySet()) {
345-
int enchantmentLevel = highestEnchantmentLevels.get(enchantment);
346-
int currentRequired = getRequiredExperienceToEnchantWithEnchantment(centerStackCopy, enchantment, enchantmentLevel, enchanterBlockEntity.canOwnerApplyConflictingEnchantments);
352+
for (var entry : highestEnchantments.getEnchantmentEntries()) {
353+
var enchantment = entry.getKey();
354+
int level = entry.getIntValue();
355+
int currentRequired = getRequiredExperienceToEnchantWithEnchantment(centerStackCopy, enchantment, level, enchanterBlockEntity.canOwnerApplyConflictingEnchantments);
347356
if (currentRequired > 0) {
348-
centerStackCopy = SpectrumEnchantmentHelper.addOrUpgradeEnchantment(centerStackCopy, enchantment, enchantmentLevel, false, enchanterBlockEntity.canOwnerApplyConflictingEnchantments).getRight();
357+
centerStackCopy = SpectrumEnchantmentHelper.addOrUpgradeEnchantment(centerStackCopy, enchantment, level, false, enchanterBlockEntity.canOwnerApplyConflictingEnchantments).getRight();
349358
requiredExperience += currentRequired;
350359
valid = true;
351360
} else {
@@ -370,8 +379,8 @@ public static int getRequiredExperienceToEnchantCenterItem(@NotNull EnchanterBlo
370379
* @param level The enchantments level
371380
* @return The required experience to enchant. -1 if the enchantment is not applicable
372381
*/
373-
public static int getRequiredExperienceToEnchantWithEnchantment(ItemStack stack, Enchantment enchantment, int level, boolean allowEnchantmentConflicts) {
374-
if (!enchantment.isAcceptableItem(stack) && !SpectrumEnchantmentHelper.isEnchantableBook(stack)) {
382+
public static int getRequiredExperienceToEnchantWithEnchantment(ItemStack stack, RegistryEntry<Enchantment> enchantment, int level, boolean allowEnchantmentConflicts) {
383+
if (!enchantment.value().isAcceptableItem(stack) && !SpectrumEnchantmentHelper.isEnchantableBook(stack)) {
375384
return -1;
376385
}
377386

@@ -380,7 +389,7 @@ public static int getRequiredExperienceToEnchantWithEnchantment(ItemStack stack,
380389
return -1;
381390
}
382391

383-
boolean conflicts = SpectrumEnchantmentHelper.hasEnchantmentThatConflictsWith(stack, enchantment);
392+
boolean conflicts = !EnchantmentHelper.isCompatible(stack.getEnchantments().getEnchantments(), enchantment);
384393
if (conflicts && !allowEnchantmentConflicts) {
385394
return -1;
386395
}
@@ -392,31 +401,26 @@ public static int getRequiredExperienceToEnchantWithEnchantment(ItemStack stack,
392401
return requiredExperience;
393402
}
394403

395-
public static Integer getEnchantingPrice(ItemStack stack, Enchantment enchantment, int level) {
404+
public static Integer getEnchantingPrice(ItemStack stack, RegistryEntry<Enchantment> enchantment, int level) {
396405
int enchantability = Math.max(1, stack.getItem().getEnchantability()); // items like Elytras have an enchantability of 0, but can get unbreaking
397-
if (enchantment.isAcceptableItem(stack) || SpectrumEnchantmentHelper.isEnchantableBook(stack)) {
406+
if (enchantment.value().isAcceptableItem(stack) || SpectrumEnchantmentHelper.isEnchantableBook(stack)) {
398407
return getRequiredExperienceForEnchantment(enchantability, enchantment, level);
399408
}
400409
return -1;
401410
}
402411

403-
public static int getRequiredExperienceForEnchantment(int enchantability, Enchantment enchantment, int level) {
412+
public static int getRequiredExperienceForEnchantment(int enchantability, RegistryEntry<Enchantment> entry, int level) {
404413
if (enchantability > 0) {
405-
int rarityCost;
406-
Enchantment.Rarity rarity = enchantment.getRarity();
407-
switch (rarity) {
408-
case COMMON -> rarityCost = 10;
409-
case UNCOMMON -> rarityCost = 25;
410-
case RARE -> rarityCost = 50;
411-
default -> rarityCost = 80;
412-
}
413-
414+
var enchantment = entry.value();
415+
416+
// Interpolated version of COMMON -> 10, UNCOMMON -> 25, RARE -> 50, VERY_RARE -> 80
417+
var rarityMults = new float[] { 0, 10, 12.5F, 12.67F, 12.5F, 12, 11.33F, 10.71F, 10 };
418+
var anvilCost = enchantment.getAnvilCost();
419+
var rarityCost = rarityMults[Math.min(anvilCost, rarityMults.length - 1)] * anvilCost;
420+
414421
float levelCost = level + ((float) level / enchantment.getMaxLevel()); // the higher the level, the pricier. But not as bad for enchantments with high max levels
415-
float specialMulti = enchantment.isTreasure() ? 2.0F : enchantment.isCursed() ? 1.5F : 1.0F;
416-
float selectionAvailabilityMod = 1.0F;
417-
if (!(enchantment instanceof SpectrumEnchantment)) {
418-
selectionAvailabilityMod = (enchantment.isAvailableForRandomSelection() ? 0.5F : 0.75F) + (enchantment.isAvailableForEnchantedBookOffer() ? 0.5F : 0.75F);
419-
}
422+
float specialMulti = entry.isIn(EnchantmentTags.TREASURE) ? 2.0F : entry.isIn(EnchantmentTags.CURSE) ? 1.5F : 1.0F;
423+
float selectionAvailabilityMod = (entry.isIn(EnchantmentTags.IN_ENCHANTING_TABLE) ? 0.5F : 0.75F) + (entry.isIn(EnchantmentTags.TRADEABLE) ? 0.5F : 0.75F);
420424
float enchantabilityMod = (4.0F / (2 + enchantability)) * 4.0F;
421425
return (int) Math.floor(rarityCost * levelCost * specialMulti * selectionAvailabilityMod * enchantabilityMod);
422426
}
@@ -548,7 +552,7 @@ private static void calculateCurrentRecipe(@NotNull World world, @NotNull Enchan
548552
}
549553

550554
enchanterBlockEntity.craftingTime = 0;
551-
GatedSpectrumRecipe previousRecipe = enchanterBlockEntity.currentRecipe;
555+
GatedSpectrumRecipe<?> previousRecipe = enchanterBlockEntity.currentRecipe;
552556
enchanterBlockEntity.currentRecipe = null;
553557
int previousOrientation = enchanterBlockEntity.virtualInventoryRecipeOrientation;
554558
boolean previousMirrored = enchanterBlockEntity.virtualInventoryRecipeMirrored;

0 commit comments

Comments
 (0)