mirror of
https://github.com/tobspr/shapez.io.git
synced 2024-10-27 20:34:29 +00:00
Refactor belt underlay feature into seperate component
This commit is contained in:
parent
f91e677f2e
commit
b3b8da04a1
@ -1,14 +1,14 @@
|
|||||||
import { globalConfig } from "../../core/config";
|
|
||||||
import { enumDirection, Vector } from "../../core/vector";
|
import { enumDirection, Vector } from "../../core/vector";
|
||||||
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
import { ItemAcceptorComponent } from "../components/item_acceptor";
|
||||||
import { ItemEjectorComponent } from "../components/item_ejector";
|
import { ItemEjectorComponent } from "../components/item_ejector";
|
||||||
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
|
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
|
||||||
import { Entity } from "../entity";
|
import { Entity } from "../entity";
|
||||||
import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
|
import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
|
||||||
import { GameRoot, enumLayer } from "../root";
|
import { GameRoot } from "../root";
|
||||||
import { enumHubGoalRewards } from "../tutorial_goals";
|
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||||
import { T } from "../../translations";
|
import { T } from "../../translations";
|
||||||
import { formatItemsPerSecond } from "../../core/utils";
|
import { formatItemsPerSecond } from "../../core/utils";
|
||||||
|
import { BeltUnderlaysComponent } from "../components/belt_underlays";
|
||||||
|
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
export const enumSplitterVariants = { compact: "compact", compactInverse: "compact-inverse" };
|
export const enumSplitterVariants = { compact: "compact", compactInverse: "compact-inverse" };
|
||||||
@ -88,6 +88,8 @@ export class MetaSplitterBuilding extends MetaBuilding {
|
|||||||
slots: [], // set later
|
slots: [], // set later
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
entity.addComponent(new BeltUnderlaysComponent({ underlays: [] }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,9 +117,9 @@ export class MetaSplitterBuilding extends MetaBuilding {
|
|||||||
{ pos: new Vector(1, 0), direction: enumDirection.top },
|
{ pos: new Vector(1, 0), direction: enumDirection.top },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
entity.components.ItemAcceptor.beltUnderlays = [
|
entity.components.BeltUnderlays.underlays = [
|
||||||
{ pos: new Vector(0, 0), direction: enumDirection.top, layer: enumLayer.regular },
|
{ pos: new Vector(0, 0), direction: enumDirection.top },
|
||||||
{ pos: new Vector(1, 0), direction: enumDirection.top, layer: enumLayer.regular },
|
{ pos: new Vector(1, 0), direction: enumDirection.top },
|
||||||
];
|
];
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -143,8 +145,8 @@ export class MetaSplitterBuilding extends MetaBuilding {
|
|||||||
{ pos: new Vector(0, 0), direction: enumDirection.top },
|
{ pos: new Vector(0, 0), direction: enumDirection.top },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
entity.components.ItemAcceptor.beltUnderlays = [
|
entity.components.BeltUnderlays.underlays = [
|
||||||
{ pos: new Vector(0, 0), direction: enumDirection.top, layer: enumLayer.regular },
|
{ pos: new Vector(0, 0), direction: enumDirection.top },
|
||||||
];
|
];
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -13,6 +13,7 @@ import { StorageComponent } from "./components/storage";
|
|||||||
import { EnergyGeneratorComponent } from "./components/energy_generator";
|
import { EnergyGeneratorComponent } from "./components/energy_generator";
|
||||||
import { WiredPinsComponent } from "./components/wired_pins";
|
import { WiredPinsComponent } from "./components/wired_pins";
|
||||||
import { EnergyConsumerComponent } from "./components/energy_consumer";
|
import { EnergyConsumerComponent } from "./components/energy_consumer";
|
||||||
|
import { BeltUnderlaysComponent } from "./components/belt_underlays";
|
||||||
|
|
||||||
export function initComponentRegistry() {
|
export function initComponentRegistry() {
|
||||||
gComponentRegistry.register(StaticMapEntityComponent);
|
gComponentRegistry.register(StaticMapEntityComponent);
|
||||||
@ -29,6 +30,7 @@ export function initComponentRegistry() {
|
|||||||
gComponentRegistry.register(EnergyGeneratorComponent);
|
gComponentRegistry.register(EnergyGeneratorComponent);
|
||||||
gComponentRegistry.register(WiredPinsComponent);
|
gComponentRegistry.register(WiredPinsComponent);
|
||||||
gComponentRegistry.register(EnergyConsumerComponent);
|
gComponentRegistry.register(EnergyConsumerComponent);
|
||||||
|
gComponentRegistry.register(BeltUnderlaysComponent);
|
||||||
|
|
||||||
// IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS
|
// IMPORTANT ^^^^^ UPDATE ENTITY COMPONENT STORAGE AFTERWARDS
|
||||||
|
|
||||||
|
44
src/js/game/components/belt_underlays.js
Normal file
44
src/js/game/components/belt_underlays.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { Component } from "../component";
|
||||||
|
import { types } from "../../savegame/serialization";
|
||||||
|
import { enumDirection, Vector } from "../../core/vector";
|
||||||
|
|
||||||
|
export class BeltUnderlaysComponent extends Component {
|
||||||
|
static getId() {
|
||||||
|
return "BeltUnderlays";
|
||||||
|
}
|
||||||
|
|
||||||
|
static getSchema() {
|
||||||
|
return {
|
||||||
|
underlays: types.array(
|
||||||
|
types.structured({
|
||||||
|
pos: types.vector,
|
||||||
|
direction: types.enum(enumDirection),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
duplicateWithoutContents() {
|
||||||
|
const beltUnderlaysCopy = [];
|
||||||
|
for (let i = 0; i < this.underlays.length; ++i) {
|
||||||
|
const underlay = this.underlays[i];
|
||||||
|
beltUnderlaysCopy.push({
|
||||||
|
pos: underlay.pos.copy(),
|
||||||
|
direction: underlay.direction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BeltUnderlaysComponent({
|
||||||
|
underlays: beltUnderlaysCopy,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} param0
|
||||||
|
* @param {Array<{pos: Vector, direction: enumDirection}>=} param0.underlays Where to render belt underlays
|
||||||
|
*/
|
||||||
|
constructor({ underlays }) {
|
||||||
|
super();
|
||||||
|
this.underlays = underlays;
|
||||||
|
}
|
||||||
|
}
|
@ -39,16 +39,6 @@ export class ItemAcceptorComponent extends Component {
|
|||||||
directions: types.array(types.enum(enumDirection)),
|
directions: types.array(types.enum(enumDirection)),
|
||||||
filter: types.nullable(types.enum(enumItemType)),
|
filter: types.nullable(types.enum(enumItemType)),
|
||||||
|
|
||||||
// TODO: MIGRATE
|
|
||||||
layer: types.enum(enumLayer),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
animated: types.bool,
|
|
||||||
beltUnderlays: types.array(
|
|
||||||
types.structured({
|
|
||||||
pos: types.vector,
|
|
||||||
direction: types.enum(enumDirection),
|
|
||||||
|
|
||||||
// TODO: MIGRATE
|
// TODO: MIGRATE
|
||||||
layer: types.enum(enumLayer),
|
layer: types.enum(enumLayer),
|
||||||
})
|
})
|
||||||
@ -68,20 +58,8 @@ export class ItemAcceptorComponent extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const beltUnderlaysCopy = [];
|
|
||||||
for (let i = 0; i < this.beltUnderlays.length; ++i) {
|
|
||||||
const underlay = this.beltUnderlays[i];
|
|
||||||
beltUnderlaysCopy.push({
|
|
||||||
pos: underlay.pos.copy(),
|
|
||||||
direction: underlay.direction,
|
|
||||||
layer: underlay.layer,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ItemAcceptorComponent({
|
return new ItemAcceptorComponent({
|
||||||
slots: slotsCopy,
|
slots: slotsCopy,
|
||||||
beltUnderlays: beltUnderlaysCopy,
|
|
||||||
animated: this.animated,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,23 +67,16 @@ export class ItemAcceptorComponent extends Component {
|
|||||||
*
|
*
|
||||||
* @param {object} param0
|
* @param {object} param0
|
||||||
* @param {Array<ItemAcceptorSlotConfig>} param0.slots The slots from which we accept items
|
* @param {Array<ItemAcceptorSlotConfig>} param0.slots The slots from which we accept items
|
||||||
* @param {boolean=} param0.animated Whether to animate item consumption
|
|
||||||
* @param {Array<{pos: Vector, direction: enumDirection, layer: enumLayer}>=} param0.beltUnderlays Where to render belt underlays
|
|
||||||
*/
|
*/
|
||||||
constructor({ slots = [], beltUnderlays = [], animated = true }) {
|
constructor({ slots = [] }) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.animated = animated;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fixes belt animations
|
* Fixes belt animations
|
||||||
* @type {Array<{ item: BaseItem, slotIndex: number, animProgress: number, direction: enumDirection }>}
|
* @type {Array<{ item: BaseItem, slotIndex: number, animProgress: number, direction: enumDirection }>}
|
||||||
*/
|
*/
|
||||||
this.itemConsumptionAnimations = [];
|
this.itemConsumptionAnimations = [];
|
||||||
|
|
||||||
/* Which belt underlays to render */
|
|
||||||
this.beltUnderlays = beltUnderlays;
|
|
||||||
|
|
||||||
this.setSlots(slots);
|
this.setSlots(slots);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,14 +135,12 @@ export class ItemAcceptorComponent extends Component {
|
|||||||
* @param {BaseItem} item
|
* @param {BaseItem} item
|
||||||
*/
|
*/
|
||||||
onItemAccepted(slotIndex, direction, item) {
|
onItemAccepted(slotIndex, direction, item) {
|
||||||
if (this.animated) {
|
this.itemConsumptionAnimations.push({
|
||||||
this.itemConsumptionAnimations.push({
|
item,
|
||||||
item,
|
slotIndex,
|
||||||
slotIndex,
|
direction,
|
||||||
direction,
|
animProgress: 0.0,
|
||||||
animProgress: 0.0,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -398,7 +398,7 @@ export class GameCore {
|
|||||||
|
|
||||||
if (!this.root.camera.getIsMapOverlayActive()) {
|
if (!this.root.camera.getIsMapOverlayActive()) {
|
||||||
// Underlays for splitters / balancers
|
// Underlays for splitters / balancers
|
||||||
systems.itemAcceptor.drawUnderlays(params, enumLayer.regular);
|
systems.beltUnderlays.drawUnderlays(params, enumLayer.regular);
|
||||||
|
|
||||||
// Belt items
|
// Belt items
|
||||||
systems.belt.drawLayerBeltItems(params, enumLayer.regular);
|
systems.belt.drawLayerBeltItems(params, enumLayer.regular);
|
||||||
|
@ -13,6 +13,7 @@ import { StorageComponent } from "./components/storage";
|
|||||||
import { EnergyGeneratorComponent } from "./components/energy_generator";
|
import { EnergyGeneratorComponent } from "./components/energy_generator";
|
||||||
import { WiredPinsComponent } from "./components/wired_pins";
|
import { WiredPinsComponent } from "./components/wired_pins";
|
||||||
import { EnergyConsumerComponent } from "./components/energy_consumer";
|
import { EnergyConsumerComponent } from "./components/energy_consumer";
|
||||||
|
import { BeltUnderlaysComponent } from "./components/belt_underlays";
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,6 +66,9 @@ export class EntityComponentStorage {
|
|||||||
/** @type {EnergyConsumerComponent} */
|
/** @type {EnergyConsumerComponent} */
|
||||||
this.EnergyConsumer;
|
this.EnergyConsumer;
|
||||||
|
|
||||||
|
/** @type {BeltUnderlaysComponent} */
|
||||||
|
this.BeltUnderlays;
|
||||||
|
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import { StorageSystem } from "./systems/storage";
|
|||||||
import { EnergyGeneratorSystem } from "./systems/energy_generator";
|
import { EnergyGeneratorSystem } from "./systems/energy_generator";
|
||||||
import { WiredPinsSystem } from "./systems/wired_pins";
|
import { WiredPinsSystem } from "./systems/wired_pins";
|
||||||
import { EnergyConsumerSystem } from "./systems/energy_consumer";
|
import { EnergyConsumerSystem } from "./systems/energy_consumer";
|
||||||
|
import { BeltUnderlaysSystem } from "./systems/belt_underlays";
|
||||||
|
|
||||||
const logger = createLogger("game_system_manager");
|
const logger = createLogger("game_system_manager");
|
||||||
|
|
||||||
@ -68,6 +69,9 @@ export class GameSystemManager {
|
|||||||
/** @type {EnergyConsumerSystem} */
|
/** @type {EnergyConsumerSystem} */
|
||||||
energyConsumer: null,
|
energyConsumer: null,
|
||||||
|
|
||||||
|
/** @type {BeltUnderlaysSystem} */
|
||||||
|
beltUnderlays: null,
|
||||||
|
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
};
|
};
|
||||||
this.systemUpdateOrder = [];
|
this.systemUpdateOrder = [];
|
||||||
@ -110,6 +114,8 @@ export class GameSystemManager {
|
|||||||
|
|
||||||
add("energyConsumer", EnergyConsumerSystem);
|
add("energyConsumer", EnergyConsumerSystem);
|
||||||
|
|
||||||
|
add("beltUnderlays", BeltUnderlaysSystem);
|
||||||
|
|
||||||
// IMPORTANT: Must be after belt system since belt system can change the
|
// IMPORTANT: Must be after belt system since belt system can change the
|
||||||
// orientation of an entity after it is placed -> the item acceptor cache
|
// orientation of an entity after it is placed -> the item acceptor cache
|
||||||
// then would be invalid
|
// then would be invalid
|
||||||
|
75
src/js/game/systems/belt_underlays.js
Normal file
75
src/js/game/systems/belt_underlays.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||||
|
import { BeltUnderlaysComponent } from "../components/belt_underlays";
|
||||||
|
import { BELT_ANIM_COUNT } from "./belt";
|
||||||
|
import { Loader } from "../../core/loader";
|
||||||
|
import { enumLayer } from "../root";
|
||||||
|
import { Entity } from "../entity";
|
||||||
|
import { enumDirectionToAngle } from "../../core/vector";
|
||||||
|
import { globalConfig } from "../../core/config";
|
||||||
|
import { drawRotatedSprite } from "../../core/draw_utils";
|
||||||
|
|
||||||
|
export class BeltUnderlaysSystem extends GameSystemWithFilter {
|
||||||
|
constructor(root) {
|
||||||
|
super(root, [BeltUnderlaysComponent]);
|
||||||
|
|
||||||
|
this.underlayBeltSprites = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < BELT_ANIM_COUNT; ++i) {
|
||||||
|
this.underlayBeltSprites.push(Loader.getSprite("sprites/belt/forward_" + i + ".png"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the acceptor underlays
|
||||||
|
* @param {import("../../core/draw_utils").DrawParameters} parameters
|
||||||
|
* @param {enumLayer} layer
|
||||||
|
*/
|
||||||
|
drawUnderlays(parameters, layer) {
|
||||||
|
this.forEachMatchingEntityOnScreen(parameters, this.drawEntityUnderlays.bind(this, layer));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {enumLayer} layer
|
||||||
|
* @param {import("../../core/draw_utils").DrawParameters} parameters
|
||||||
|
* @param {Entity} entity
|
||||||
|
*/
|
||||||
|
drawEntityUnderlays(layer, parameters, entity) {
|
||||||
|
const staticComp = entity.components.StaticMapEntity;
|
||||||
|
const underlayComp = entity.components.BeltUnderlays;
|
||||||
|
|
||||||
|
if (entity.layer !== layer) {
|
||||||
|
// Not our layer
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!staticComp.shouldBeDrawn(parameters)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit speed to avoid belts going backwards
|
||||||
|
const speedMultiplier = Math.min(this.root.hubGoals.getBeltBaseSpeed(layer), 10);
|
||||||
|
|
||||||
|
const underlays = underlayComp.underlays;
|
||||||
|
for (let i = 0; i < underlays.length; ++i) {
|
||||||
|
const { pos, direction } = underlays[i];
|
||||||
|
|
||||||
|
const transformedPos = staticComp.localTileToWorld(pos);
|
||||||
|
const angle = enumDirectionToAngle[staticComp.localDirectionToWorld(direction)];
|
||||||
|
|
||||||
|
// SYNC with systems/belt.js:drawSingleEntity!
|
||||||
|
const animationIndex = Math.floor(
|
||||||
|
((this.root.time.realtimeNow() * speedMultiplier * BELT_ANIM_COUNT * 126) / 42) *
|
||||||
|
globalConfig.beltItemSpacingByLayer[layer]
|
||||||
|
);
|
||||||
|
|
||||||
|
drawRotatedSprite({
|
||||||
|
parameters,
|
||||||
|
sprite: this.underlayBeltSprites[animationIndex % this.underlayBeltSprites.length],
|
||||||
|
x: (transformedPos.x + 0.5) * globalConfig.tileSize,
|
||||||
|
y: (transformedPos.y + 0.5) * globalConfig.tileSize,
|
||||||
|
angle: Math.radians(angle),
|
||||||
|
size: globalConfig.tileSize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,12 +13,6 @@ import { enumLayer } from "../root";
|
|||||||
export class ItemAcceptorSystem extends GameSystemWithFilter {
|
export class ItemAcceptorSystem extends GameSystemWithFilter {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
super(root, [ItemAcceptorComponent]);
|
super(root, [ItemAcceptorComponent]);
|
||||||
|
|
||||||
this.underlayBeltSprites = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < BELT_ANIM_COUNT; ++i) {
|
|
||||||
this.underlayBeltSprites.push(Loader.getSprite("sprites/belt/forward_" + i + ".png"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
@ -59,15 +53,6 @@ export class ItemAcceptorSystem extends GameSystemWithFilter {
|
|||||||
this.forEachMatchingEntityOnScreen(parameters, this.drawEntityRegularLayer.bind(this, layer));
|
this.forEachMatchingEntityOnScreen(parameters, this.drawEntityRegularLayer.bind(this, layer));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the acceptor underlays
|
|
||||||
* @param {DrawParameters} parameters
|
|
||||||
* @param {enumLayer} layer
|
|
||||||
*/
|
|
||||||
drawUnderlays(parameters, layer) {
|
|
||||||
this.forEachMatchingEntityOnScreen(parameters, this.drawEntityUnderlays.bind(this, layer));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {enumLayer} layer
|
* @param {enumLayer} layer
|
||||||
* @param {DrawParameters} parameters
|
* @param {DrawParameters} parameters
|
||||||
@ -105,48 +90,4 @@ export class ItemAcceptorSystem extends GameSystemWithFilter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {enumLayer} layer
|
|
||||||
* @param {DrawParameters} parameters
|
|
||||||
* @param {Entity} entity
|
|
||||||
*/
|
|
||||||
drawEntityUnderlays(layer, parameters, entity) {
|
|
||||||
const staticComp = entity.components.StaticMapEntity;
|
|
||||||
const acceptorComp = entity.components.ItemAcceptor;
|
|
||||||
|
|
||||||
if (!staticComp.shouldBeDrawn(parameters)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limit speed to avoid belts going backwards
|
|
||||||
const speedMultiplier = Math.min(this.root.hubGoals.getBeltBaseSpeed(layer), 10);
|
|
||||||
|
|
||||||
const underlays = acceptorComp.beltUnderlays;
|
|
||||||
for (let i = 0; i < underlays.length; ++i) {
|
|
||||||
const { pos, direction, layer: underlayLayer } = underlays[i];
|
|
||||||
if (underlayLayer !== layer) {
|
|
||||||
// Not our layer
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const transformedPos = staticComp.localTileToWorld(pos);
|
|
||||||
const angle = enumDirectionToAngle[staticComp.localDirectionToWorld(direction)];
|
|
||||||
|
|
||||||
// SYNC with systems/belt.js:drawSingleEntity!
|
|
||||||
const animationIndex = Math.floor(
|
|
||||||
((this.root.time.realtimeNow() * speedMultiplier * BELT_ANIM_COUNT * 126) / 42) *
|
|
||||||
globalConfig.beltItemSpacingByLayer[layer]
|
|
||||||
);
|
|
||||||
|
|
||||||
drawRotatedSprite({
|
|
||||||
parameters,
|
|
||||||
sprite: this.underlayBeltSprites[animationIndex % this.underlayBeltSprites.length],
|
|
||||||
x: (transformedPos.x + 0.5) * globalConfig.tileSize,
|
|
||||||
y: (transformedPos.y + 0.5) * globalConfig.tileSize,
|
|
||||||
angle: Math.radians(angle),
|
|
||||||
size: globalConfig.tileSize,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user