mirror of
https://github.com/architectury/architectury-api.git
synced 2026-03-30 05:05:19 -05:00
Merge remote-tracking branch 'architectury/feature/registry-creation-api' into 1.16
This commit is contained in:
@@ -25,5 +25,5 @@ import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
/**
|
||||
* The equivalent of {@link RecipeSerializer} to use in common that has forge registry entries extended.
|
||||
*/
|
||||
public abstract class AbstractRecipeSerializer<T extends Recipe<?>> implements RecipeSerializer<T> {
|
||||
public abstract class AbstractRecipeSerializer<T extends Recipe<?>> extends RegistryEntry<T> implements RecipeSerializer<T> {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* This file is part of architectury.
|
||||
* Copyright (C) 2020, 2021 shedaniel
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package me.shedaniel.architectury.core;
|
||||
|
||||
/**
|
||||
* An entry in registries, will extend {@code ForgeRegistryEntry} on forge.
|
||||
*/
|
||||
public class RegistryEntry<T> {
|
||||
}
|
||||
@@ -43,7 +43,6 @@ public interface ClientLifecycleEvent {
|
||||
Event<ClientWorldState> CLIENT_WORLD_LOAD = EventFactory.createLoop();
|
||||
Event<ClientState> CLIENT_SETUP = EventFactory.createLoop();
|
||||
|
||||
@Deprecated
|
||||
@Environment(EnvType.CLIENT)
|
||||
interface ClientState extends LifecycleEvent.InstanceState<Minecraft> {}
|
||||
|
||||
|
||||
@@ -20,9 +20,12 @@
|
||||
package me.shedaniel.architectury.registry;
|
||||
|
||||
import me.shedaniel.architectury.annotations.ExpectPlatform;
|
||||
import me.shedaniel.architectury.core.RegistryEntry;
|
||||
import me.shedaniel.architectury.registry.registries.RegistryBuilder;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -36,6 +39,7 @@ public final class Registries {
|
||||
private final RegistryProvider provider;
|
||||
private final String modId;
|
||||
|
||||
@NotNull
|
||||
public static Registries get(String modId) {
|
||||
return REGISTRIES.computeIfAbsent(modId, Registries::new);
|
||||
}
|
||||
@@ -45,15 +49,24 @@ public final class Registries {
|
||||
this.modId = modId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public <T> Registry<T> get(ResourceKey<net.minecraft.core.Registry<T>> key) {
|
||||
return this.provider.get(key);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Deprecated
|
||||
public <T> Registry<T> get(net.minecraft.core.Registry<T> registry) {
|
||||
return this.provider.get(registry);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SafeVarargs
|
||||
public final <T extends RegistryEntry<T>> RegistryBuilder<T> builder(ResourceLocation registryId, T... typeGetter) {
|
||||
if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!");
|
||||
return this.provider.builder((Class<T>) typeGetter.getClass().getComponentType(), registryId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forge: If the object is {@code IForgeRegistryEntry}, use `getRegistryName`, else null
|
||||
* Fabric: Use registry
|
||||
@@ -79,8 +92,8 @@ public final class Registries {
|
||||
* Forge: If the object is {@code IForgeRegistryEntry}, use `getRegistryName`, else null
|
||||
* Fabric: null
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
@Deprecated
|
||||
public static <T> ResourceLocation getRegistryName(T object) {
|
||||
return getId(object, (ResourceKey<net.minecraft.core.Registry<T>>) null);
|
||||
}
|
||||
@@ -90,6 +103,7 @@ public final class Registries {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getModId() {
|
||||
return modId;
|
||||
}
|
||||
@@ -100,5 +114,7 @@ public final class Registries {
|
||||
|
||||
@Deprecated
|
||||
<T> Registry<T> get(net.minecraft.core.Registry<T> registry);
|
||||
|
||||
<T extends RegistryEntry<T>> RegistryBuilder<T> builder(Class<T> type, ResourceLocation registryId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,11 +49,16 @@ public interface Registry<T> extends Iterable<T> {
|
||||
@Nullable
|
||||
ResourceLocation getId(T obj);
|
||||
|
||||
int getRawId(T obj);
|
||||
|
||||
Optional<ResourceKey<T>> getKey(T obj);
|
||||
|
||||
@Nullable
|
||||
T get(ResourceLocation id);
|
||||
|
||||
@Nullable
|
||||
T byRawId(int rawId);
|
||||
|
||||
boolean contains(ResourceLocation id);
|
||||
|
||||
boolean containsValue(T obj);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* This file is part of architectury.
|
||||
* Copyright (C) 2020, 2021 shedaniel
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package me.shedaniel.architectury.registry.registries;
|
||||
|
||||
import me.shedaniel.architectury.core.RegistryEntry;
|
||||
import me.shedaniel.architectury.registry.Registry;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface RegistryBuilder<T extends RegistryEntry<T>> {
|
||||
@NotNull
|
||||
Registry<T> build();
|
||||
|
||||
@NotNull
|
||||
RegistryBuilder<T> option(@NotNull RegistryOption option);
|
||||
|
||||
@NotNull
|
||||
default RegistryBuilder<T> saveToDisc() {
|
||||
return option(StandardRegistryOption.SAVE_TO_DISC);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
default RegistryBuilder<T> syncToClients() {
|
||||
return option(StandardRegistryOption.SYNC_TO_CLIENTS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* This file is part of architectury.
|
||||
* Copyright (C) 2020, 2021 shedaniel
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package me.shedaniel.architectury.registry.registries;
|
||||
|
||||
public interface RegistryOption {
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* This file is part of architectury.
|
||||
* Copyright (C) 2020, 2021 shedaniel
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package me.shedaniel.architectury.registry.registries;
|
||||
|
||||
public enum StandardRegistryOption implements RegistryOption {
|
||||
/**
|
||||
* Denote that the registry should save to disc and persist. Defaulted false.
|
||||
*/
|
||||
SAVE_TO_DISC,
|
||||
/**
|
||||
* Denote that the registry should sync its contents to clients. Defaulted false.
|
||||
*/
|
||||
SYNC_TO_CLIENTS,
|
||||
}
|
||||
@@ -20,9 +20,16 @@
|
||||
package me.shedaniel.architectury.registry.fabric;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import me.shedaniel.architectury.core.RegistryEntry;
|
||||
import me.shedaniel.architectury.registry.Registries;
|
||||
import me.shedaniel.architectury.registry.Registry;
|
||||
import me.shedaniel.architectury.registry.RegistrySupplier;
|
||||
import me.shedaniel.architectury.registry.registries.RegistryBuilder;
|
||||
import me.shedaniel.architectury.registry.registries.RegistryOption;
|
||||
import me.shedaniel.architectury.registry.registries.StandardRegistryOption;
|
||||
import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder;
|
||||
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.LazyLoadedValue;
|
||||
@@ -64,6 +71,36 @@ public class RegistriesImpl {
|
||||
public <T> Registry<T> get(net.minecraft.core.Registry<T> registry) {
|
||||
return new RegistryImpl<>(registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public <T extends RegistryEntry<T>> RegistryBuilder<T> builder(Class<T> type, ResourceLocation registryId) {
|
||||
return new RegistryBuilderWrapper<>(FabricRegistryBuilder.createSimple(type, registryId));
|
||||
}
|
||||
}
|
||||
|
||||
public static class RegistryBuilderWrapper<T extends RegistryEntry<T>> implements RegistryBuilder<T> {
|
||||
@NotNull
|
||||
private FabricRegistryBuilder<T, MappedRegistry<T>> builder;
|
||||
|
||||
public RegistryBuilderWrapper(@NotNull FabricRegistryBuilder<T, MappedRegistry<T>> builder) {
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Registry<T> build() {
|
||||
return RegistryProviderImpl.INSTANCE.get(builder.buildAndRegister());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull RegistryBuilder<T> option(@NotNull RegistryOption option) {
|
||||
if (option == StandardRegistryOption.SAVE_TO_DISC) {
|
||||
this.builder.attribute(RegistryAttribute.PERSISTED);
|
||||
} else if (option == StandardRegistryOption.SYNC_TO_CLIENTS) {
|
||||
this.builder.attribute(RegistryAttribute.SYNCED);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RegistryImpl<T> implements Registry<T> {
|
||||
@@ -128,6 +165,11 @@ public class RegistriesImpl {
|
||||
return delegate.getKey(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawId(T obj) {
|
||||
return delegate.getId(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ResourceKey<T>> getKey(T obj) {
|
||||
return delegate.getResourceKey(obj);
|
||||
@@ -138,6 +180,11 @@ public class RegistriesImpl {
|
||||
return delegate.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T byRawId(int rawId) {
|
||||
return delegate.byId(rawId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(ResourceLocation id) {
|
||||
return delegate.keySet().contains(id);
|
||||
|
||||
@@ -39,7 +39,7 @@ public class ReloadListenersImpl {
|
||||
public static void registerReloadListener(PackType type, PreparableReloadListener listener) {
|
||||
byte[] bytes = new byte[8];
|
||||
RANDOM.nextBytes(bytes);
|
||||
ResourceLocation id = new ResourceLocation("architectury:reload_" + StringUtils.leftPad(Math.abs(Longs.fromByteArray(bytes)) + "", 19));
|
||||
ResourceLocation id = new ResourceLocation("architectury:reload_" + StringUtils.leftPad(Math.abs(Longs.fromByteArray(bytes)) + "", 19, '0'));
|
||||
ResourceManagerHelper.get(type).registerReloadListener(new IdentifiableResourceReloadListener() {
|
||||
@Override
|
||||
public ResourceLocation getFabricId() {
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
package me.shedaniel.architectury.mixin.forge;
|
||||
|
||||
import me.shedaniel.architectury.core.AbstractRecipeSerializer;
|
||||
import me.shedaniel.architectury.core.RegistryEntry;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(AbstractRecipeSerializer.class)
|
||||
public class MixinAbstractRecipeSerializer {
|
||||
@Mixin(RegistryEntry.class)
|
||||
public class MixinRegistryEntry<T> {
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public class ArchitecturyMixinPlugin implements IMixinConfigPlugin {
|
||||
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
|
||||
// Inject our own sugar
|
||||
switch (mixinClassName) {
|
||||
case "me.shedaniel.architectury.mixin.forge.MixinAbstractRecipeSerializer":
|
||||
case "me.shedaniel.architectury.mixin.forge.MixinRegistryEntry":
|
||||
targetClass.superName = "net/minecraftforge/registries/ForgeRegistryEntry";
|
||||
for (MethodNode method : targetClass.methods) {
|
||||
if (Objects.equals(method.name, "<init>")) {
|
||||
@@ -77,9 +77,7 @@ public class ArchitecturyMixinPlugin implements IMixinConfigPlugin {
|
||||
}
|
||||
}
|
||||
String recipeSerializer = targetClass.interfaces.get(0);
|
||||
if (targetClass.signature != null) {
|
||||
targetClass.signature = targetClass.signature.replace("Ljava/lang/Object;", "Lnet/minecraftforge/registries/ForgeRegistryEntry<L" + recipeSerializer + "<*>;>");
|
||||
}
|
||||
targetClass.signature = "<T::Lnet/minecraftforge/registries/IForgeRegistryEntry<TT;>;>Lnet/minecraftforge/registries/ForgeRegistryEntry<TT;>;";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,14 @@ package me.shedaniel.architectury.registry.forge;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Table;
|
||||
import me.shedaniel.architectury.core.RegistryEntry;
|
||||
import me.shedaniel.architectury.platform.forge.EventBuses;
|
||||
import me.shedaniel.architectury.registry.Registries;
|
||||
import me.shedaniel.architectury.registry.Registry;
|
||||
import me.shedaniel.architectury.registry.RegistrySupplier;
|
||||
import me.shedaniel.architectury.registry.registries.RegistryBuilder;
|
||||
import me.shedaniel.architectury.registry.registries.RegistryOption;
|
||||
import me.shedaniel.architectury.registry.registries.StandardRegistryOption;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.LazyLoadedValue;
|
||||
@@ -33,6 +37,7 @@ import net.minecraftforge.event.RegistryEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.RegistryObject;
|
||||
import net.minecraftforge.registries.ForgeRegistry;
|
||||
import net.minecraftforge.registries.IForgeRegistry;
|
||||
import net.minecraftforge.registries.IForgeRegistryEntry;
|
||||
import net.minecraftforge.registries.RegistryManager;
|
||||
@@ -76,7 +81,11 @@ public class RegistriesImpl {
|
||||
|
||||
@Override
|
||||
public <T> Registry<T> get(ResourceKey<net.minecraft.core.Registry<T>> registryKey) {
|
||||
return new ForgeBackedRegistryImpl<>(registry, (IForgeRegistry) RegistryManager.ACTIVE.getRegistry(registryKey.location()));
|
||||
return get(RegistryManager.ACTIVE.getRegistry(registryKey.location()));
|
||||
}
|
||||
|
||||
public <T> Registry<T> get(IForgeRegistry registry) {
|
||||
return new ForgeBackedRegistryImpl<>(this.registry, registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,6 +93,13 @@ public class RegistriesImpl {
|
||||
return new VanillaBackedRegistryImpl<>(registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends RegistryEntry<T>> RegistryBuilder<T> builder(Class<T> type, ResourceLocation registryId) {
|
||||
return new RegistryBuilderWrapper<>(this, new net.minecraftforge.registries.RegistryBuilder<>()
|
||||
.setName(registryId)
|
||||
.setType((Class) type));
|
||||
}
|
||||
|
||||
public class EventListener {
|
||||
@SubscribeEvent
|
||||
public void handleEvent(RegistryEvent.Register event) {
|
||||
@@ -101,6 +117,37 @@ public class RegistriesImpl {
|
||||
}
|
||||
}
|
||||
|
||||
public static class RegistryBuilderWrapper<T extends RegistryEntry<T>> implements RegistryBuilder<T> {
|
||||
@NotNull
|
||||
private final RegistryProviderImpl provider;
|
||||
@NotNull
|
||||
private final net.minecraftforge.registries.RegistryBuilder<?> builder;
|
||||
private boolean saveToDisk = false;
|
||||
private boolean syncToClients = false;
|
||||
|
||||
public RegistryBuilderWrapper(@NotNull RegistryProviderImpl provider, @NotNull net.minecraftforge.registries.RegistryBuilder<?> builder) {
|
||||
this.provider = provider;
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Registry<T> build() {
|
||||
if (!syncToClients) builder.disableSync();
|
||||
if (!saveToDisk) builder.disableSaving();
|
||||
return provider.get(builder.create());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull RegistryBuilder<T> option(@NotNull RegistryOption option) {
|
||||
if (option == StandardRegistryOption.SAVE_TO_DISC) {
|
||||
this.saveToDisk = true;
|
||||
} else if (option == StandardRegistryOption.SYNC_TO_CLIENTS) {
|
||||
this.syncToClients = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class VanillaBackedRegistryImpl<T> implements Registry<T> {
|
||||
private net.minecraft.core.Registry<T> delegate;
|
||||
|
||||
@@ -164,6 +211,11 @@ public class RegistriesImpl {
|
||||
return delegate.getKey(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawId(T obj) {
|
||||
return delegate.getId(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ResourceKey<T>> getKey(T t) {
|
||||
return delegate.getResourceKey(t);
|
||||
@@ -175,6 +227,11 @@ public class RegistriesImpl {
|
||||
return delegate.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T byRawId(int rawId) {
|
||||
return delegate.byId(rawId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(ResourceLocation resourceLocation) {
|
||||
return delegate.keySet().contains(resourceLocation);
|
||||
@@ -310,6 +367,11 @@ public class RegistriesImpl {
|
||||
return delegate.getKey(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawId(T obj) {
|
||||
return ((ForgeRegistry<T>) delegate).getID(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ResourceKey<T>> getKey(T t) {
|
||||
return Optional.ofNullable(getId(t)).map(id -> ResourceKey.create(key(), id));
|
||||
@@ -321,6 +383,11 @@ public class RegistriesImpl {
|
||||
return delegate.getValue(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T byRawId(int rawId) {
|
||||
return ((ForgeRegistry<T>) delegate).getValue(rawId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(ResourceLocation resourceLocation) {
|
||||
return delegate.containsKey(resourceLocation);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"client": [
|
||||
],
|
||||
"mixins": [
|
||||
"BiomeGenerationSettingsBuilderAccessor", "MixinAbstractRecipeSerializer", "MixinBlockEntity", "MixinBlockEntityExtension",
|
||||
"BiomeGenerationSettingsBuilderAccessor", "MixinRegistryEntry", "MixinBlockEntity", "MixinBlockEntityExtension",
|
||||
"MobSpawnSettingsBuilderAccessor"
|
||||
],
|
||||
"injectors": {
|
||||
|
||||
Reference in New Issue
Block a user