tuned up sounds to get rid of static instance.
This commit is contained in:
parent
7796a23ad9
commit
658b65e216
@ -107,11 +107,11 @@ export class Rigplatform {
|
|||||||
new PhysicsAggregate(
|
new PhysicsAggregate(
|
||||||
this.rigMesh,
|
this.rigMesh,
|
||||||
PhysicsShapeType.CYLINDER,
|
PhysicsShapeType.CYLINDER,
|
||||||
{friction: 1, center: Vector3.Zero(), radius: .5, mass: 10, restitution: .01},
|
{friction: 0, center: Vector3.Zero(), radius: .5, mass: 10, restitution: .01},
|
||||||
scene);
|
scene);
|
||||||
|
|
||||||
rigAggregate.body.setMotionType(PhysicsMotionType.DYNAMIC);
|
rigAggregate.body.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||||
rigAggregate.body.setGravityFactor(.001);
|
rigAggregate.body.setGravityFactor(.02);
|
||||||
this.fixRotation();
|
this.fixRotation();
|
||||||
this.body = rigAggregate.body;
|
this.body = rigAggregate.body;
|
||||||
|
|
||||||
@ -191,7 +191,7 @@ export class Rigplatform {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
private fixRotation() {
|
private fixRotation() {
|
||||||
this.scene.registerBeforeRender(() => {
|
this.scene.onAfterPhysicsObservable.add(() => {
|
||||||
if (AppConfig?.config?.currentTurnSnap?.value > 0) {
|
if (AppConfig?.config?.currentTurnSnap?.value > 0) {
|
||||||
const q = this.rigMesh.rotationQuaternion;
|
const q = this.rigMesh.rotationQuaternion;
|
||||||
this.body.setAngularVelocity(Vector3.Zero());
|
this.body.setAngularVelocity(Vector3.Zero());
|
||||||
@ -203,7 +203,6 @@ export class Rigplatform {
|
|||||||
} else {
|
} else {
|
||||||
this.body.setAngularVelocity(Vector3.Up().scale(this.turnVelocity));
|
this.body.setAngularVelocity(Vector3.Up().scale(this.turnVelocity));
|
||||||
}
|
}
|
||||||
|
}, -1, false, this, false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,6 +28,7 @@ export class DiagramManager {
|
|||||||
private persistenceManager: IPersistenceManager = null;
|
private persistenceManager: IPersistenceManager = null;
|
||||||
private readonly scene: Scene;
|
private readonly scene: Scene;
|
||||||
private xr: WebXRExperienceHelper;
|
private xr: WebXRExperienceHelper;
|
||||||
|
private sounds: DiaSounds;
|
||||||
|
|
||||||
|
|
||||||
public setPersistenceManager(persistenceManager: IPersistenceManager) {
|
public setPersistenceManager(persistenceManager: IPersistenceManager) {
|
||||||
@ -48,12 +49,13 @@ export class DiagramManager {
|
|||||||
private controllers: Controllers;
|
private controllers: Controllers;
|
||||||
|
|
||||||
constructor(scene: Scene, xr: WebXRExperienceHelper, controllers: Controllers) {
|
constructor(scene: Scene, xr: WebXRExperienceHelper, controllers: Controllers) {
|
||||||
|
this.sounds = new DiaSounds(scene);
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.xr = xr;
|
this.xr = xr;
|
||||||
this.controllers = controllers;
|
this.controllers = controllers;
|
||||||
this.actionManager = new ActionManager(this.scene);
|
this.actionManager = new ActionManager(this.scene);
|
||||||
this.actionManager.registerAction(
|
this.actionManager.registerAction(
|
||||||
new PlaySoundAction(ActionManager.OnPointerOverTrigger, DiaSounds.instance.tick));
|
new PlaySoundAction(ActionManager.OnPointerOverTrigger, this.sounds.tick));
|
||||||
this.actionManager.registerAction(
|
this.actionManager.registerAction(
|
||||||
new ExecuteCodeAction(ActionManager.OnPointerOverTrigger, (evt) => {
|
new ExecuteCodeAction(ActionManager.OnPointerOverTrigger, (evt) => {
|
||||||
this.controllers.controllerObserver.notifyObservers({
|
this.controllers.controllerObserver.notifyObservers({
|
||||||
@ -106,7 +108,7 @@ export class DiagramManager {
|
|||||||
newMesh.material = mesh.material;
|
newMesh.material = mesh.material;
|
||||||
|
|
||||||
newMesh.metadata = this.deepCopy(mesh.metadata);
|
newMesh.metadata = this.deepCopy(mesh.metadata);
|
||||||
DiagramShapePhysics.applyPhysics(newMesh, this.scene);
|
DiagramShapePhysics.applyPhysics(this.sounds, newMesh, this.scene);
|
||||||
this.persistenceManager.add(newMesh);
|
this.persistenceManager.add(newMesh);
|
||||||
return newMesh;
|
return newMesh;
|
||||||
}
|
}
|
||||||
@ -143,7 +145,7 @@ export class DiagramManager {
|
|||||||
if (event.parent) {
|
if (event.parent) {
|
||||||
mesh.parent = this.scene.getMeshById(event.parent);
|
mesh.parent = this.scene.getMeshById(event.parent);
|
||||||
}
|
}
|
||||||
DiagramShapePhysics.applyPhysics(mesh, this.scene, PhysicsMotionType.DYNAMIC);
|
DiagramShapePhysics.applyPhysics(this.sounds, mesh, this.scene, PhysicsMotionType.DYNAMIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onDiagramEvent(event: DiagramEvent) {
|
private onDiagramEvent(event: DiagramEvent) {
|
||||||
@ -165,7 +167,7 @@ export class DiagramManager {
|
|||||||
mesh = MeshConverter.fromDiagramEntity(event.entity, this.scene);
|
mesh = MeshConverter.fromDiagramEntity(event.entity, this.scene);
|
||||||
if (mesh) {
|
if (mesh) {
|
||||||
mesh.actionManager = this.actionManager;
|
mesh.actionManager = this.actionManager;
|
||||||
DiagramShapePhysics.applyPhysics(mesh, this.scene, PhysicsMotionType.DYNAMIC);
|
DiagramShapePhysics.applyPhysics(this.sounds, mesh, this.scene, PhysicsMotionType.DYNAMIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -188,12 +190,12 @@ export class DiagramManager {
|
|||||||
mesh.actionManager = this.actionManager;
|
mesh.actionManager = this.actionManager;
|
||||||
}
|
}
|
||||||
DiagramShapePhysics
|
DiagramShapePhysics
|
||||||
.applyPhysics(mesh, this.scene);
|
.applyPhysics(this.sounds, mesh, this.scene);
|
||||||
break;
|
break;
|
||||||
case DiagramEventType.MODIFY:
|
case DiagramEventType.MODIFY:
|
||||||
this.getPersistenceManager()?.modify(mesh);
|
this.getPersistenceManager()?.modify(mesh);
|
||||||
DiagramShapePhysics
|
DiagramShapePhysics
|
||||||
.applyPhysics(mesh, this.scene);
|
.applyPhysics(this.sounds, mesh, this.scene);
|
||||||
break;
|
break;
|
||||||
case DiagramEventType.CHANGECOLOR:
|
case DiagramEventType.CHANGECOLOR:
|
||||||
if (!event.oldColor) {
|
if (!event.oldColor) {
|
||||||
@ -219,7 +221,7 @@ export class DiagramManager {
|
|||||||
this.getPersistenceManager()?.remove(mesh)
|
this.getPersistenceManager()?.remove(mesh)
|
||||||
mesh?.physicsBody?.dispose();
|
mesh?.physicsBody?.dispose();
|
||||||
mesh.dispose();
|
mesh.dispose();
|
||||||
DiaSounds.instance.exit.play();
|
this.sounds.exit.play();
|
||||||
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -231,7 +233,7 @@ export class DiagramManager {
|
|||||||
class DiagramShapePhysics {
|
class DiagramShapePhysics {
|
||||||
private static logger: log.Logger = log.getLogger('DiagramShapePhysics');
|
private static logger: log.Logger = log.getLogger('DiagramShapePhysics');
|
||||||
|
|
||||||
public static applyPhysics(mesh: AbstractMesh, scene: Scene, motionType?: PhysicsMotionType) {
|
public static applyPhysics(sounds: DiaSounds, mesh: AbstractMesh, scene: Scene, motionType?: PhysicsMotionType) {
|
||||||
if (!AppConfig.config.physicsEnabled) {
|
if (!AppConfig.config.physicsEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -285,10 +287,13 @@ class DiagramShapePhysics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
body.setCollisionCallbackEnabled(true);
|
body.setCollisionCallbackEnabled(true);
|
||||||
body.getCollisionObservable().add((event, state) => {
|
body.getCollisionObservable().add((event) => {
|
||||||
if (event.distance > .001 && !DiaSounds.instance.low.isPlaying) {
|
|
||||||
this.logger.debug(event, state);
|
if (event.impulse < 10 && event.impulse > 1) {
|
||||||
DiaSounds.instance.low.play();
|
const sound = sounds.bounce;
|
||||||
|
sound.setVolume(event.impulse / 10);
|
||||||
|
sound.attachToMesh(mesh);
|
||||||
|
sound.play();
|
||||||
}
|
}
|
||||||
}, -1, false, this);
|
}, -1, false, this);
|
||||||
//body.setMotionType(PhysicsMotionType.ANIMATED);
|
//body.setMotionType(PhysicsMotionType.ANIMATED);
|
||||||
|
|||||||
@ -8,12 +8,14 @@ import {DiaSounds} from "../util/diaSounds";
|
|||||||
import {BaseMenu} from "./baseMenu";
|
import {BaseMenu} from "./baseMenu";
|
||||||
|
|
||||||
export class ConfigMenu extends BaseMenu {
|
export class ConfigMenu extends BaseMenu {
|
||||||
|
private sounds: DiaSounds;
|
||||||
private configPlane: AbstractMesh = null;
|
private configPlane: AbstractMesh = null;
|
||||||
|
|
||||||
private yObserver;
|
private yObserver;
|
||||||
|
|
||||||
constructor(scene: Scene, xr: WebXRExperienceHelper, controllers: Controllers) {
|
constructor(scene: Scene, xr: WebXRExperienceHelper, controllers: Controllers) {
|
||||||
super(scene, xr, controllers);
|
super(scene, xr, controllers);
|
||||||
|
this.sounds = new DiaSounds(scene);
|
||||||
if (!this.yObserver) {
|
if (!this.yObserver) {
|
||||||
this.controllers.controllerObserver.add((event) => {
|
this.controllers.controllerObserver.add((event) => {
|
||||||
if (event.type == 'y-button') {
|
if (event.type == 'y-button') {
|
||||||
@ -26,12 +28,12 @@ export class ConfigMenu extends BaseMenu {
|
|||||||
|
|
||||||
public toggle() {
|
public toggle() {
|
||||||
if (this.configPlane) {
|
if (this.configPlane) {
|
||||||
DiaSounds.instance.exit.play();
|
this.sounds.exit.play();
|
||||||
this.configPlane.dispose();
|
this.configPlane.dispose();
|
||||||
this.configPlane = null;
|
this.configPlane = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DiaSounds.instance.enter.play();
|
this.sounds.enter.play();
|
||||||
const width = .25;
|
const width = .25;
|
||||||
const height = .75;
|
const height = .75;
|
||||||
const res = 256;
|
const res = 256;
|
||||||
|
|||||||
@ -37,6 +37,7 @@ export class EditMenu {
|
|||||||
private connection: DiagramConnection = null;
|
private connection: DiagramConnection = null;
|
||||||
private panel: PlanePanel;
|
private panel: PlanePanel;
|
||||||
private buttonMaterial: StandardMaterial;
|
private buttonMaterial: StandardMaterial;
|
||||||
|
private sounds: DiaSounds;
|
||||||
|
|
||||||
private get isVisible(): boolean {
|
private get isVisible(): boolean {
|
||||||
return this.panel.isVisible;
|
return this.panel.isVisible;
|
||||||
@ -49,37 +50,10 @@ export class EditMenu {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle() {
|
|
||||||
if (this.isVisible) {
|
|
||||||
DiaSounds.instance.exit.play();
|
|
||||||
this.isVisible = false;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
DiaSounds.instance.enter.play();
|
|
||||||
CameraHelper.setMenuPosition(this.manager.rootContainer.children[0].node, this.scene);
|
|
||||||
this.isVisible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private getTool(template: string, color: Color3): Mesh {
|
|
||||||
const baseMeshId = 'tool-' + template + '-' + color.toHexString();
|
|
||||||
return (this.scene.getMeshById(baseMeshId) as Mesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
private persist(mesh: AbstractMesh, text: string) {
|
|
||||||
if (mesh.metadata) {
|
|
||||||
mesh.metadata.text = text;
|
|
||||||
} else {
|
|
||||||
this.logger.error("mesh has no metadata");
|
|
||||||
}
|
|
||||||
this.diagramManager.onDiagramEventObservable.notifyObservers({
|
|
||||||
type: DiagramEventType.MODIFY,
|
|
||||||
entity: MeshConverter.toDiagramEntity(mesh),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(scene: Scene, xr: WebXRDefaultExperience, diagramManager: DiagramManager) {
|
constructor(scene: Scene, xr: WebXRDefaultExperience, diagramManager: DiagramManager) {
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.xr = xr;
|
this.xr = xr;
|
||||||
|
this.sounds = new DiaSounds(scene);
|
||||||
this.diagramManager = diagramManager;
|
this.diagramManager = diagramManager;
|
||||||
this.gizmoManager = new GizmoManager(scene);
|
this.gizmoManager = new GizmoManager(scene);
|
||||||
this.gizmoManager.boundingBoxGizmoEnabled = true;
|
this.gizmoManager.boundingBoxGizmoEnabled = true;
|
||||||
@ -132,6 +106,34 @@ export class EditMenu {
|
|||||||
this.panel = panel;
|
this.panel = panel;
|
||||||
this.isVisible = false;
|
this.isVisible = false;
|
||||||
}
|
}
|
||||||
|
private getTool(template: string, color: Color3): Mesh {
|
||||||
|
const baseMeshId = 'tool-' + template + '-' + color.toHexString();
|
||||||
|
return (this.scene.getMeshById(baseMeshId) as Mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
private persist(mesh: AbstractMesh, text: string) {
|
||||||
|
if (mesh.metadata) {
|
||||||
|
mesh.metadata.text = text;
|
||||||
|
} else {
|
||||||
|
this.logger.error("mesh has no metadata");
|
||||||
|
}
|
||||||
|
this.diagramManager.onDiagramEventObservable.notifyObservers({
|
||||||
|
type: DiagramEventType.MODIFY,
|
||||||
|
entity: MeshConverter.toDiagramEntity(mesh),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
if (this.isVisible) {
|
||||||
|
this.sounds.exit.play();
|
||||||
|
this.isVisible = false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.sounds.enter.play();
|
||||||
|
CameraHelper.setMenuPosition(this.manager.rootContainer.children[0].node, this.scene);
|
||||||
|
this.isVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
makeButton(name: string, id: string) {
|
makeButton(name: string, id: string) {
|
||||||
const button = new Button3D(name);
|
const button = new Button3D(name);
|
||||||
@ -296,7 +298,7 @@ export class EditMenu {
|
|||||||
this.showNewRelic();
|
this.showNewRelic();
|
||||||
break;
|
break;
|
||||||
case "export":
|
case "export":
|
||||||
GLTF2Export.GLTFAsync(this.scene, 'diagram.gltf', {
|
GLTF2Export.GLBAsync(this.scene, 'diagram.glb', {
|
||||||
shouldExportNode: function (node) {
|
shouldExportNode: function (node) {
|
||||||
if (node?.metadata?.template) {
|
if (node?.metadata?.template) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -25,12 +25,14 @@ export class Introduction {
|
|||||||
private step: number = 0;
|
private step: number = 0;
|
||||||
private items: AbstractMesh[] = [];
|
private items: AbstractMesh[] = [];
|
||||||
private advance: Button3D;
|
private advance: Button3D;
|
||||||
|
private sounds: DiaSounds;
|
||||||
|
|
||||||
constructor(scene: Scene) {
|
constructor(scene: Scene) {
|
||||||
|
this.sounds = new DiaSounds(scene);
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.manager = new GUI3DManager(scene);
|
this.manager = new GUI3DManager(scene);
|
||||||
this.physicsHelper = new PhysicsHelper(scene);
|
this.physicsHelper = new PhysicsHelper(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
public start() {
|
public start() {
|
||||||
this.scene.physicsEnabled = true;
|
this.scene.physicsEnabled = true;
|
||||||
this.advance = new Button3D("advance");
|
this.advance = new Button3D("advance");
|
||||||
@ -96,8 +98,19 @@ export class Introduction {
|
|||||||
restitution: .1
|
restitution: .1
|
||||||
}, this.scene);
|
}, this.scene);
|
||||||
aggregate.body.getCollisionObservable().add((collider) => {
|
aggregate.body.getCollisionObservable().add((collider) => {
|
||||||
if (collider.impulse > .4) {
|
if (collider.impulse < .5) {
|
||||||
DiaSounds.instance.low.play();
|
return;
|
||||||
|
}
|
||||||
|
let volume = 0;
|
||||||
|
const sound = this.sounds.bounce;
|
||||||
|
if (collider.impulse > 1 && collider.impulse < 10) {
|
||||||
|
volume = collider.impulse / 10;
|
||||||
|
}
|
||||||
|
if (volume > 0) {
|
||||||
|
sound.attachToMesh(aggregate.body.transformNode);
|
||||||
|
sound.updateOptions({offset: 0, volume: volume, length: .990});
|
||||||
|
console.log(volume);
|
||||||
|
sound.play();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
aggregate.body.setCollisionCallbackEnabled(true);
|
aggregate.body.setCollisionCallbackEnabled(true);
|
||||||
|
|||||||
@ -5,49 +5,11 @@ export class DiaSounds {
|
|||||||
|
|
||||||
private readonly _birds: Sound;
|
private readonly _birds: Sound;
|
||||||
|
|
||||||
private static _instance: DiaSounds;
|
|
||||||
|
|
||||||
public static get instance() {
|
|
||||||
return DiaSounds._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get tick() {
|
public get tick() {
|
||||||
return new Sound("tick", '/assets/sounds/tick.mp3', this.scene);
|
return new Sound("tick", '/assets/sounds/tick.mp3', this.scene);
|
||||||
}
|
}
|
||||||
|
private volume: number = 0.8;
|
||||||
constructor(scene: Scene) {
|
private readonly _bounce;
|
||||||
this.scene = scene;
|
|
||||||
this._enter = new Sound("enter", "/assets/sounds/sounds.mp3", this.scene, null, {
|
|
||||||
autoplay: false,
|
|
||||||
loop: false,
|
|
||||||
offset: 0,
|
|
||||||
length: 1.0
|
|
||||||
});
|
|
||||||
this._exit = new Sound("exit", "/assets/sounds/sounds.mp3", this.scene, null, {
|
|
||||||
autoplay: false,
|
|
||||||
loop: false,
|
|
||||||
offset: 1,
|
|
||||||
length: 1.0
|
|
||||||
});
|
|
||||||
this._high = new Sound("high", "/assets/sounds/sounds.mp3", this.scene, null, {
|
|
||||||
autoplay: false,
|
|
||||||
loop: false,
|
|
||||||
offset: 2,
|
|
||||||
length: 1.0
|
|
||||||
});
|
|
||||||
this._low = new Sound("low", "/assets/sounds/sounds.mp3", this.scene, null, {
|
|
||||||
autoplay: false,
|
|
||||||
loop: false,
|
|
||||||
offset: 3,
|
|
||||||
length: 1.0
|
|
||||||
});
|
|
||||||
this._birds = new Sound("birds", "/assets/sounds/birds.mp3", this.scene, null, {
|
|
||||||
autoplay: true,
|
|
||||||
loop: true
|
|
||||||
});
|
|
||||||
//this._enter.autoplay = true;
|
|
||||||
DiaSounds._instance = this;
|
|
||||||
}
|
|
||||||
private readonly _enter: Sound;
|
private readonly _enter: Sound;
|
||||||
|
|
||||||
public get enter() {
|
public get enter() {
|
||||||
@ -71,4 +33,60 @@ export class DiaSounds {
|
|||||||
public get low() {
|
public get low() {
|
||||||
return this._low;
|
return this._low;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor(scene: Scene) {
|
||||||
|
this.scene = scene;
|
||||||
|
this._enter = new Sound("enter", "/assets/sounds/sounds.mp3", this.scene, null, {
|
||||||
|
autoplay: false,
|
||||||
|
loop: false,
|
||||||
|
volume: this.volume,
|
||||||
|
offset: 0,
|
||||||
|
length: 1.0
|
||||||
|
});
|
||||||
|
this._exit = new Sound("exit", "/assets/sounds/sounds.mp3", this.scene, null, {
|
||||||
|
autoplay: false,
|
||||||
|
loop: false,
|
||||||
|
offset: 1,
|
||||||
|
volume: this.volume,
|
||||||
|
length: 1.0
|
||||||
|
});
|
||||||
|
this._high = new Sound("high", "/assets/sounds/sounds.mp3", this.scene, null, {
|
||||||
|
autoplay: false,
|
||||||
|
loop: false,
|
||||||
|
offset: 2,
|
||||||
|
volume: this.volume,
|
||||||
|
length: 1.0
|
||||||
|
});
|
||||||
|
this._low = new Sound("low", "/assets/sounds/sounds.mp3", this.scene, null, {
|
||||||
|
autoplay: false,
|
||||||
|
loop: false,
|
||||||
|
offset: 3,
|
||||||
|
volume: this.volume,
|
||||||
|
length: 1.0
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
this._bounce = new Sound("bounce", "/assets/sounds/drumsprite.mp3", this.scene, null, {
|
||||||
|
autoplay: false,
|
||||||
|
loop: false,
|
||||||
|
offset: 0,
|
||||||
|
length: 0.990
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
this._birds = new Sound("birds", "/assets/sounds/birds.mp3", this.scene, null, {
|
||||||
|
autoplay: true,
|
||||||
|
loop: true
|
||||||
|
});
|
||||||
|
//this._enter.autoplay = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get bounce() {
|
||||||
|
const bounce = this._bounce.clone();
|
||||||
|
bounce.updateOptions({offset: 0, volume: this.volume, length: .990});
|
||||||
|
bounce.onEndedObservable.add(() => {
|
||||||
|
bounce.dispose();
|
||||||
|
});
|
||||||
|
return bounce;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user