Cut more chaff
This commit is contained in:
parent
93ce164ff9
commit
1b0fd21481
@ -1,7 +1,6 @@
|
|||||||
package io.github.cottonmc.templates;
|
package io.github.cottonmc.templates;
|
||||||
|
|
||||||
import io.github.cottonmc.templates.model.SlopeUnbakedModel;
|
import io.github.cottonmc.templates.model.SlopeUnbakedModel;
|
||||||
import io.github.cottonmc.templates.model.TemplateModelVariantProvider;
|
|
||||||
import net.fabricmc.api.ClientModInitializer;
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
|
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
|
||||||
import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry;
|
import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry;
|
||||||
@ -20,7 +19,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class TemplatesClient implements ClientModInitializer {
|
public class TemplatesClient implements ClientModInitializer {
|
||||||
public static TemplateModelVariantProvider provider = new TemplateModelVariantProvider();
|
public static TemplatesModelProvider provider = new TemplatesModelProvider();
|
||||||
|
|
||||||
public static @NotNull Renderer getFabricRenderer() {
|
public static @NotNull Renderer getFabricRenderer() {
|
||||||
return Objects.requireNonNull(RendererAccess.INSTANCE.getRenderer(), "A Fabric Rendering API implementation is required to use Templates!");
|
return Objects.requireNonNull(RendererAccess.INSTANCE.getRenderer(), "A Fabric Rendering API implementation is required to use Templates!");
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package io.github.cottonmc.templates.model;
|
package io.github.cottonmc.templates;
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.client.model.ModelProviderContext;
|
import net.fabricmc.fabric.api.client.model.ModelProviderContext;
|
||||||
import net.fabricmc.fabric.api.client.model.ModelProviderException;
|
import net.fabricmc.fabric.api.client.model.ModelProviderException;
|
||||||
@ -15,7 +15,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class TemplateModelVariantProvider implements ModelResourceProvider, ModelVariantProvider {
|
public class TemplatesModelProvider implements ModelResourceProvider, ModelVariantProvider {
|
||||||
private final Map<Identifier, Supplier<UnbakedModel>> factories = new HashMap<>();
|
private final Map<Identifier, Supplier<UnbakedModel>> factories = new HashMap<>();
|
||||||
private final Map<ModelIdentifier, Identifier> itemAssignments = new HashMap<>();
|
private final Map<ModelIdentifier, Identifier> itemAssignments = new HashMap<>();
|
||||||
|
|
@ -3,8 +3,6 @@ package io.github.cottonmc.templates.model;
|
|||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||||
import net.minecraft.util.math.AffineTransformation;
|
import net.minecraft.util.math.AffineTransformation;
|
||||||
import net.minecraft.util.math.AffineTransformations;
|
|
||||||
import net.minecraft.util.math.Direction;
|
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
import org.joml.Vector4f;
|
import org.joml.Vector4f;
|
||||||
@ -14,8 +12,6 @@ public record AffineQuadTransformer(Matrix4f affineMatrix) implements RenderCont
|
|||||||
this(aff.getMatrix());
|
this(aff.getMatrix());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final AffineQuadTransformer EAST = new AffineQuadTransformer(AffineTransformations.DIRECTION_ROTATIONS.get(Direction.EAST));
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean transform(MutableQuadView quad) {
|
public boolean transform(MutableQuadView quad) {
|
||||||
Vector3f pos3 = new Vector3f();
|
Vector3f pos3 = new Vector3f();
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
package io.github.cottonmc.templates.model;
|
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
|
||||||
import net.minecraft.block.BlockState;
|
|
||||||
import net.minecraft.client.render.model.BakedModel;
|
|
||||||
import net.minecraft.item.ItemStack;
|
|
||||||
import net.minecraft.util.math.AffineTransformation;
|
|
||||||
import net.minecraft.util.math.BlockPos;
|
|
||||||
import net.minecraft.util.math.random.Random;
|
|
||||||
import net.minecraft.world.BlockRenderView;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public final class SlopeBakedModel extends ForwardingBakedModel {
|
|
||||||
public SlopeBakedModel(BakedModel baseModel, TemplateAppearanceManager tam, AffineTransformation aff) {
|
|
||||||
this.wrapped = baseModel;
|
|
||||||
|
|
||||||
this.preparer = new SlopeQuadTransformFactory(tam);
|
|
||||||
this.blockAffineTransformer = new AffineQuadTransformer(aff);
|
|
||||||
this.itemAffineTransformer = AffineQuadTransformer.EAST; //Makes items point the same way as stairs. Kinda clunky
|
|
||||||
this.baseMesh = SlopeBaseMesh.make();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final TemplateQuadTransformFactory preparer;
|
|
||||||
private final AffineQuadTransformer blockAffineTransformer;
|
|
||||||
private final AffineQuadTransformer itemAffineTransformer;
|
|
||||||
private final Mesh baseMesh;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isVanillaAdapter() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
|
|
||||||
context.pushTransform(blockAffineTransformer);
|
|
||||||
context.pushTransform(preparer.blockTransformer(blockView, state, pos, randomSupplier));
|
|
||||||
context.meshConsumer().accept(baseMesh);
|
|
||||||
context.popTransform();
|
|
||||||
context.popTransform();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
|
|
||||||
context.pushTransform(itemAffineTransformer);
|
|
||||||
context.pushTransform(preparer.itemTransformer(stack, randomSupplier));
|
|
||||||
context.meshConsumer().accept(baseMesh);
|
|
||||||
context.popTransform();
|
|
||||||
context.popTransform();
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,6 @@ package io.github.cottonmc.templates.model;
|
|||||||
|
|
||||||
import io.github.cottonmc.templates.TemplatesClient;
|
import io.github.cottonmc.templates.TemplatesClient;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.Renderer;
|
import net.fabricmc.fabric.api.renderer.v1.Renderer;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
|
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
|
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
||||||
@ -10,7 +9,7 @@ import net.minecraft.util.math.Direction;
|
|||||||
|
|
||||||
public class SlopeBaseMesh {
|
public class SlopeBaseMesh {
|
||||||
/**
|
/**
|
||||||
* @see SlopeQuadTransformFactory.Transformer for why these values were chosen
|
* @see TemplateBakedModel.RetexturingTransformer for why these values were chosen
|
||||||
*/
|
*/
|
||||||
public static final int TAG_SLOPE = Direction.UP.ordinal();
|
public static final int TAG_SLOPE = Direction.UP.ordinal();
|
||||||
public static final int TAG_LEFT = Direction.EAST.ordinal();
|
public static final int TAG_LEFT = Direction.EAST.ordinal();
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
package io.github.cottonmc.templates.model;
|
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
|
||||||
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
|
|
||||||
import net.minecraft.block.BlockState;
|
|
||||||
import net.minecraft.block.Blocks;
|
|
||||||
import net.minecraft.client.color.block.BlockColorProvider;
|
|
||||||
import net.minecraft.client.texture.Sprite;
|
|
||||||
import net.minecraft.item.BlockItem;
|
|
||||||
import net.minecraft.item.ItemStack;
|
|
||||||
import net.minecraft.nbt.NbtCompound;
|
|
||||||
import net.minecraft.nbt.NbtHelper;
|
|
||||||
import net.minecraft.registry.Registries;
|
|
||||||
import net.minecraft.util.math.BlockPos;
|
|
||||||
import net.minecraft.util.math.Direction;
|
|
||||||
import net.minecraft.util.math.random.Random;
|
|
||||||
import net.minecraft.world.BlockRenderView;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
@SuppressWarnings("ClassCanBeRecord")
|
|
||||||
public class SlopeQuadTransformFactory implements TemplateQuadTransformFactory {
|
|
||||||
public SlopeQuadTransformFactory(TemplateAppearanceManager tam) {
|
|
||||||
this.tam = tam;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final TemplateAppearanceManager tam;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull RenderContext.QuadTransform blockTransformer(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier) {
|
|
||||||
BlockState template = (((RenderAttachedBlockView) blockView).getBlockEntityRenderAttachment(pos) instanceof BlockState s) ? s : null;
|
|
||||||
if(template == null || template.isAir()) return new Transformer(tam.getDefaultAppearance(), 0xFFFFFFFF);
|
|
||||||
|
|
||||||
BlockColorProvider prov = ColorProviderRegistry.BLOCK.get(template.getBlock());
|
|
||||||
int globalTint = prov != null ? prov.getColor(state, blockView, pos, 1) : 0xFFFFFFFF;
|
|
||||||
return new Transformer(tam.getAppearance(template), globalTint);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull RenderContext.QuadTransform itemTransformer(ItemStack stack, Supplier<Random> randomSupplier) {
|
|
||||||
//cheeky: if the item has NBT data, pluck out the blockstate from it
|
|
||||||
NbtCompound tag = BlockItem.getBlockEntityNbt(stack);
|
|
||||||
if(tag != null && tag.contains("BlockState")) {
|
|
||||||
BlockState state = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), tag.getCompound("BlockState"));
|
|
||||||
if(!state.isAir()) return new Transformer(tam.getAppearance(state), 0xFFFFFFFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Transformer(tam.getDefaultAppearance(), 0xFFFFFFFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static record Transformer(TemplateAppearance appearance, int color) implements RenderContext.QuadTransform {
|
|
||||||
private static final Direction[] DIRECTIONS = Direction.values();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean transform(MutableQuadView quad) {
|
|
||||||
quad.material(appearance.getRenderMaterial());
|
|
||||||
|
|
||||||
//The quad tag numbers were selected so this magic trick works:
|
|
||||||
Direction dir = DIRECTIONS[quad.tag()];
|
|
||||||
|
|
||||||
//TODO: this newly-simplified direction passing to hasColor is almost certainly incorrect
|
|
||||||
// I think hasColor was kinda incorrect in the first place tho
|
|
||||||
if(appearance.hasColor(dir)) quad.color(color, color, color, color);
|
|
||||||
Sprite sprite = appearance.getSprite(dir);
|
|
||||||
|
|
||||||
quad.spriteBake(sprite, MutableQuadView.BAKE_NORMALIZED);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -27,12 +27,14 @@ public class SlopeUnbakedModel implements UnbakedModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> spriteLookup, ModelBakeSettings modelBakeSettings, Identifier identifier) {
|
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> spriteLookup, ModelBakeSettings modelBakeSettings, Identifier identifier) {
|
||||||
//TODO: this is weird, should use my own model instead
|
//TODO: this is weird, should use my own model instead.
|
||||||
BakedModel baseModel = baker.bake(BlockModels.getModelId(Blocks.SANDSTONE_STAIRS.getDefaultState()), modelBakeSettings);
|
// I should also adjust the item frame/first-person rotations (previously I used SANDSTONE_STAIRS, which has models/block/stairs.json as a parent,
|
||||||
|
// and that one brings some extra custom rotations along for the ride
|
||||||
|
BakedModel baseModel = baker.bake(BlockModels.getModelId(Blocks.SANDSTONE.getDefaultState()), modelBakeSettings);
|
||||||
|
|
||||||
//TODO: push this up (it's just a cache of data sourced from blockmodels, and can be cached until resource-reload)
|
//TODO: push this up (it's just a cache of data sourced from blockmodels, and can be shared among *all* templates/cached until resource-reload)
|
||||||
TemplateAppearanceManager tam = new TemplateAppearanceManager(spriteLookup);
|
TemplateAppearanceManager tam = new TemplateAppearanceManager(spriteLookup);
|
||||||
|
|
||||||
return new SlopeBakedModel(baseModel, tam, modelBakeSettings.getRotation());
|
return new TemplateBakedModel(baseModel, tam, modelBakeSettings.getRotation(), SlopeBaseMesh.make());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
package io.github.cottonmc.templates.model;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||||
|
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.client.color.block.BlockColorProvider;
|
||||||
|
import net.minecraft.client.render.model.BakedModel;
|
||||||
|
import net.minecraft.client.texture.Sprite;
|
||||||
|
import net.minecraft.item.BlockItem;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.nbt.NbtCompound;
|
||||||
|
import net.minecraft.nbt.NbtHelper;
|
||||||
|
import net.minecraft.registry.Registries;
|
||||||
|
import net.minecraft.util.math.AffineTransformation;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
|
import net.minecraft.util.math.random.Random;
|
||||||
|
import net.minecraft.world.BlockRenderView;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public final class TemplateBakedModel extends ForwardingBakedModel {
|
||||||
|
public TemplateBakedModel(BakedModel baseModel, TemplateAppearanceManager tam, AffineTransformation aff, Mesh baseMesh) {
|
||||||
|
this.wrapped = baseModel;
|
||||||
|
|
||||||
|
this.tam = tam;
|
||||||
|
this.affineTransformer = new AffineQuadTransformer(aff);
|
||||||
|
this.baseMesh = baseMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final TemplateAppearanceManager tam;
|
||||||
|
private final AffineQuadTransformer affineTransformer;
|
||||||
|
private final Mesh baseMesh;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVanillaAdapter() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
|
||||||
|
context.pushTransform(affineTransformer);
|
||||||
|
context.pushTransform(retexturingBlockTransformer(blockView, state, pos, randomSupplier));
|
||||||
|
context.meshConsumer().accept(baseMesh);
|
||||||
|
context.popTransform();
|
||||||
|
context.popTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
|
||||||
|
context.pushTransform(affineTransformer);
|
||||||
|
context.pushTransform(retexturingItemTransformer(stack, randomSupplier));
|
||||||
|
context.meshConsumer().accept(baseMesh);
|
||||||
|
context.popTransform();
|
||||||
|
context.popTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSideLit() {
|
||||||
|
//Makes item models look less bad. TODO: possibly a weird spot to put this. corresponds to `gui_light: front` in the json
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull RenderContext.QuadTransform retexturingBlockTransformer(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier) {
|
||||||
|
BlockState template = (((RenderAttachedBlockView) blockView).getBlockEntityRenderAttachment(pos) instanceof BlockState s) ? s : null;
|
||||||
|
if(template == null || template.isAir()) return new RetexturingTransformer(tam.getDefaultAppearance(), 0xFFFFFFFF);
|
||||||
|
|
||||||
|
BlockColorProvider prov = ColorProviderRegistry.BLOCK.get(template.getBlock());
|
||||||
|
int globalTint = prov != null ? prov.getColor(state, blockView, pos, 1) : 0xFFFFFFFF;
|
||||||
|
return new RetexturingTransformer(tam.getAppearance(template), globalTint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull RenderContext.QuadTransform retexturingItemTransformer(ItemStack stack, Supplier<Random> randomSupplier) {
|
||||||
|
//cheeky: if the item has NBT data, pluck out the blockstate from it
|
||||||
|
NbtCompound tag = BlockItem.getBlockEntityNbt(stack);
|
||||||
|
if(tag != null && tag.contains("BlockState")) {
|
||||||
|
BlockState state = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), tag.getCompound("BlockState"));
|
||||||
|
if(!state.isAir()) return new RetexturingTransformer(tam.getAppearance(state), 0xFFFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RetexturingTransformer(tam.getDefaultAppearance(), 0xFFFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record RetexturingTransformer(TemplateAppearance appearance, int color) implements RenderContext.QuadTransform {
|
||||||
|
private static final Direction[] DIRECTIONS = Direction.values();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean transform(MutableQuadView quad) {
|
||||||
|
quad.material(appearance.getRenderMaterial());
|
||||||
|
|
||||||
|
//The quad tag numbers were selected so this magic trick works:
|
||||||
|
Direction dir = DIRECTIONS[quad.tag()];
|
||||||
|
|
||||||
|
//TODO: this newly-simplified direction passing to hasColor is almost certainly incorrect
|
||||||
|
// I think hasColor was kinda incorrect in the first place tho
|
||||||
|
if(appearance.hasColor(dir)) quad.color(color, color, color, color);
|
||||||
|
Sprite sprite = appearance.getSprite(dir);
|
||||||
|
|
||||||
|
quad.spriteBake(sprite, MutableQuadView.BAKE_NORMALIZED);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
package io.github.cottonmc.templates.model;
|
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
|
||||||
import net.minecraft.block.BlockState;
|
|
||||||
import net.minecraft.item.ItemStack;
|
|
||||||
import net.minecraft.util.math.BlockPos;
|
|
||||||
import net.minecraft.util.math.random.Random;
|
|
||||||
import net.minecraft.world.BlockRenderView;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Please keep thread-safety in mind - `getQuads`/`emitBlockQuads` can be called concurrently from multiple worker threads.
|
|
||||||
*/
|
|
||||||
public interface TemplateQuadTransformFactory {
|
|
||||||
@NotNull RenderContext.QuadTransform blockTransformer(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier);
|
|
||||||
@NotNull RenderContext.QuadTransform itemTransformer(ItemStack stack, Supplier<Random> randomSupplier);
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user