mirror of
https://github.com/jneug/zeichenmaschine.git
synced 2026-04-14 06:33:34 +02:00
Updated ParticleEmitters and Factories
This commit is contained in:
@@ -191,6 +191,22 @@ public class Vector extends Point2D.Double {
|
|||||||
return new Vector(x, y);
|
return new Vector(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getIntX() {
|
||||||
|
return (int)this.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRoundedX() {
|
||||||
|
return (int)Math.round(this.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIntY() {
|
||||||
|
return (int)this.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRoundedY() {
|
||||||
|
return (int)Math.round(this.y);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die Komponenten dieses Vektors neu.
|
* Setzt die Komponenten dieses Vektors neu.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -14,20 +14,12 @@ public class BasicParticle extends Particle {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BasicParticle(Color startColor) {
|
public BasicParticle( Color startColor ) {
|
||||||
this(0, startColor, null);
|
this(startColor, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BasicParticle(Color startColor, Color finalColor) {
|
public BasicParticle( Color startColor, Color finalColor ) {
|
||||||
this(0, startColor, finalColor);
|
super();
|
||||||
}
|
|
||||||
|
|
||||||
public BasicParticle( int pLifetime ) {
|
|
||||||
super(pLifetime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BasicParticle( int pLifetime, Color startColor, Color finalColor ) {
|
|
||||||
super(pLifetime);
|
|
||||||
|
|
||||||
this.color = startColor;
|
this.color = startColor;
|
||||||
this.startColor = startColor;
|
this.startColor = startColor;
|
||||||
@@ -59,8 +51,8 @@ public class BasicParticle extends Particle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void spawn( Vector pPosition, Vector pVelocity ) {
|
public void spawn( int pLifetime, Vector pPosition, Vector pVelocity ) {
|
||||||
super.spawn(pPosition, pVelocity);
|
super.spawn(pLifetime, pPosition, pVelocity);
|
||||||
this.color = this.startColor;
|
this.color = this.startColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +60,7 @@ public class BasicParticle extends Particle {
|
|||||||
public void update( double delta ) {
|
public void update( double delta ) {
|
||||||
super.update(delta);
|
super.update(delta);
|
||||||
|
|
||||||
if( startColor != null && finalColor != null ) {
|
if( isActive() && startColor != null && finalColor != null ) {
|
||||||
double t = 1.0 - lifetime / maxLifetime;
|
double t = 1.0 - lifetime / maxLifetime;
|
||||||
this.color = Color.interpolate(startColor, finalColor, t);
|
this.color = Color.interpolate(startColor, finalColor, t);
|
||||||
}
|
}
|
||||||
@@ -76,9 +68,9 @@ public class BasicParticle extends Particle {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw( Graphics2D graphics ) {
|
public void draw( Graphics2D graphics ) {
|
||||||
if( this.color != null ) {
|
if( isActive() && this.color != null ) {
|
||||||
graphics.setColor(this.color.getJavaColor());
|
graphics.setColor(this.color.getJavaColor());
|
||||||
graphics.fillOval((int) position.x, (int) position.y, 6, 6);
|
graphics.fillOval(position.getIntX() - 3, position.getIntY() - 3, 6, 6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,16 +4,18 @@ import schule.ngb.zm.Color;
|
|||||||
|
|
||||||
public class BasicParticleFactory implements ParticleFactory {
|
public class BasicParticleFactory implements ParticleFactory {
|
||||||
|
|
||||||
|
|
||||||
private final Color startColor;
|
private final Color startColor;
|
||||||
|
|
||||||
private final Color finalColor;
|
private final Color finalColor;
|
||||||
|
|
||||||
private final int maxLifetime = 50;
|
private boolean fadeOut = true;
|
||||||
|
|
||||||
public BasicParticleFactory() {
|
public BasicParticleFactory() {
|
||||||
this.startColor = new Color(128, 128, 129);
|
this(null, null);
|
||||||
this.finalColor = new Color(128, 128, 129, 0);
|
}
|
||||||
|
|
||||||
|
public BasicParticleFactory( Color startColor ) {
|
||||||
|
this(startColor, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BasicParticleFactory( Color startColor, Color finalColor ) {
|
public BasicParticleFactory( Color startColor, Color finalColor ) {
|
||||||
@@ -21,13 +23,21 @@ public class BasicParticleFactory implements ParticleFactory {
|
|||||||
this.finalColor = finalColor;
|
this.finalColor = finalColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMaxLifetime() {
|
public void setFadeOut( boolean pFadeOut ) {
|
||||||
return maxLifetime;
|
this.fadeOut = pFadeOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Particle createParticle() {
|
public Particle createParticle() {
|
||||||
return new BasicParticle(maxLifetime, startColor, finalColor);
|
Color finalClr = finalColor;
|
||||||
|
if( fadeOut ) {
|
||||||
|
if( finalColor != null ) {
|
||||||
|
finalClr = new Color(finalColor, 0);
|
||||||
|
} else if( startColor != null ) {
|
||||||
|
finalClr = new Color(startColor, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new BasicParticle(startColor, finalClr);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package schule.ngb.zm.particles;
|
||||||
|
|
||||||
|
import schule.ngb.zm.util.Log;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class GenericParticleFactory<T extends Particle> implements ParticleFactory {
|
||||||
|
|
||||||
|
private final Class<T> type;
|
||||||
|
|
||||||
|
private final Supplier<T> supplier;
|
||||||
|
|
||||||
|
public GenericParticleFactory( Class<T> type, Object... params ) {
|
||||||
|
this.type = type;
|
||||||
|
|
||||||
|
// Create paramTypes array once
|
||||||
|
Class<?>[] paramTypes = new Class<?>[params.length];
|
||||||
|
for( int i = 0; i < params.length; i++ ) {
|
||||||
|
paramTypes[i] = params[i].getClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.supplier = () -> {
|
||||||
|
T p = null;
|
||||||
|
try {
|
||||||
|
p = GenericParticleFactory.this.type.getDeclaredConstructor(paramTypes).newInstance(params);
|
||||||
|
} catch( Exception ex ) {
|
||||||
|
LOG.error( ex,
|
||||||
|
"Unable to create new Particle of type %s",
|
||||||
|
GenericParticleFactory.this.type.getCanonicalName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenericParticleFactory( Supplier<T> supplier ) {
|
||||||
|
this.supplier = supplier;
|
||||||
|
this.type = (Class<T>)supplier.get().getClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Particle createParticle() {
|
||||||
|
return this.supplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Log LOG = Log.getLogger(GenericParticleFactory.class);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,25 +7,19 @@ import schule.ngb.zm.Vector;
|
|||||||
|
|
||||||
public abstract class Particle extends PhysicsObject implements Updatable, Drawable {
|
public abstract class Particle extends PhysicsObject implements Updatable, Drawable {
|
||||||
|
|
||||||
protected double maxLifetime = 0;
|
protected double maxLifetime = 0, lifetime = 0;
|
||||||
|
|
||||||
protected double lifetime = 0;
|
|
||||||
|
|
||||||
|
|
||||||
public Particle() {
|
public Particle() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Particle( int pLifetime ) {
|
public void spawn( int pLifetime, Vector pPosition, Vector pVelocity ) {
|
||||||
super();
|
this.maxLifetime = pLifetime;
|
||||||
maxLifetime = pLifetime;
|
this.lifetime = pLifetime;
|
||||||
}
|
this.position = pPosition.copy();
|
||||||
|
this.velocity = pVelocity.copy();
|
||||||
public void spawn( Vector pPosition, Vector pVelocity ) {
|
this.acceleration = new Vector();
|
||||||
lifetime = maxLifetime;
|
|
||||||
position = pPosition.copy();
|
|
||||||
velocity = pVelocity.copy();
|
|
||||||
acceleration = new Vector();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -59,6 +53,8 @@ public abstract class Particle extends PhysicsObject implements Updatable, Drawa
|
|||||||
super.update(delta);
|
super.update(delta);
|
||||||
// lifetime -= delta;
|
// lifetime -= delta;
|
||||||
lifetime -= 1;
|
lifetime -= 1;
|
||||||
|
|
||||||
|
// TODO: (ngb) calculate delta based on lifetime?
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,20 +25,23 @@ public class ParticleEmitter implements Updatable, Drawable {
|
|||||||
|
|
||||||
public Vector direction = new Vector();
|
public Vector direction = new Vector();
|
||||||
|
|
||||||
|
public double strength = 100.0;
|
||||||
|
|
||||||
public int angle = 0;
|
public int angle = 0;
|
||||||
|
|
||||||
public double randomness = 0.0;
|
public double randomness = 0.0;
|
||||||
|
|
||||||
// private Vortex vortex = null;
|
// private Vortex vortex = null;
|
||||||
|
|
||||||
public ParticleEmitter( double pX, double pY, int pParticlesPerFrame, ParticleFactory pFactory ) {
|
public ParticleEmitter( double pX, double pY, int pParticleLifetime, int pParticlesPerFrame, ParticleFactory pFactory ) {
|
||||||
this.position = new Vector(pX, pY);
|
this.position = new Vector(pX, pY);
|
||||||
this.particlesPerFrame = pParticlesPerFrame;
|
this.particlesPerFrame = pParticlesPerFrame;
|
||||||
|
this.particleLifetime = pParticleLifetime;
|
||||||
this.particleFactory = pFactory;
|
this.particleFactory = pFactory;
|
||||||
|
|
||||||
// Create particle pool
|
// Create particle pool
|
||||||
this.particles = new Particle[particlesPerFrame * pFactory.getMaxLifetime()];
|
this.particles = new Particle[particlesPerFrame * pParticleLifetime];
|
||||||
this.direction = Vector.random(8, 16).setLength(100);
|
this.direction = Vector.random(8, 16).normalize();
|
||||||
|
|
||||||
// vortex = new Vortex(position.copy().add(-10, -10), -.2, 8);
|
// vortex = new Vortex(position.copy().add(-10, -10), -.2, 8);
|
||||||
}
|
}
|
||||||
@@ -54,6 +57,8 @@ public class ParticleEmitter implements Updatable, Drawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
|
this.direction.normalize();
|
||||||
|
|
||||||
// Partikel initialisieren
|
// Partikel initialisieren
|
||||||
for( int i = 0; i < particles.length; i++ ) {
|
for( int i = 0; i < particles.length; i++ ) {
|
||||||
particles[i] = particleFactory.createParticle();
|
particles[i] = particleFactory.createParticle();
|
||||||
@@ -84,16 +89,13 @@ public class ParticleEmitter implements Updatable, Drawable {
|
|||||||
int ppf = particlesPerFrame;
|
int ppf = particlesPerFrame;
|
||||||
Particle nextParticle = getNextParticle();
|
Particle nextParticle = getNextParticle();
|
||||||
while( ppf > 0 && nextParticle != null ) {
|
while( ppf > 0 && nextParticle != null ) {
|
||||||
// TODO: (ngb) randomize lifetime of particles in Factory?
|
int lifetime = (int) random(particleLifetime);
|
||||||
// int pLeben = (int) random(particleLifetime);
|
|
||||||
// p.lifetime = pLeben;
|
|
||||||
// p.maxLifetime = pLeben;
|
|
||||||
|
|
||||||
double rotation = (angle / 2.0) - (int) (Math.random() * angle);
|
double rotation = (angle / 2.0) - (int) (Math.random() * angle);
|
||||||
Vector velocity = direction.copy().rotate(rotation);
|
Vector velocity = direction.copy().scale(strength).rotate(rotation);
|
||||||
velocity.scale(random());
|
velocity.scale(random());
|
||||||
|
|
||||||
nextParticle.spawn(this.position, velocity);
|
nextParticle.spawn(lifetime, this.position, velocity);
|
||||||
nextParticle = getNextParticle();
|
nextParticle = getNextParticle();
|
||||||
ppf -= 1;
|
ppf -= 1;
|
||||||
}
|
}
|
||||||
@@ -130,7 +132,7 @@ public class ParticleEmitter implements Updatable, Drawable {
|
|||||||
public void draw( Graphics2D graphics ) {
|
public void draw( Graphics2D graphics ) {
|
||||||
java.awt.Color current = graphics.getColor();
|
java.awt.Color current = graphics.getColor();
|
||||||
for( Particle particle : particles ) {
|
for( Particle particle : particles ) {
|
||||||
if( particle != null ) {
|
if( particle != null && particle.isVisible() ) {
|
||||||
particle.draw(graphics);
|
particle.draw(graphics);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,4 @@ public interface ParticleFactory {
|
|||||||
|
|
||||||
Particle createParticle();
|
Particle createParticle();
|
||||||
|
|
||||||
int getMaxLifetime();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/main/java/schule/ngb/zm/particles/StarParticle.java
Normal file
35
src/main/java/schule/ngb/zm/particles/StarParticle.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package schule.ngb.zm.particles;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Color;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
|
||||||
|
public class StarParticle extends BasicParticle {
|
||||||
|
|
||||||
|
public StarParticle() {
|
||||||
|
super();
|
||||||
|
this.startColor = Color.PURE_GREEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StarParticle( Color startColor ) {
|
||||||
|
this(startColor, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StarParticle( Color startColor, Color finalColor ) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.color = startColor;
|
||||||
|
this.startColor = startColor;
|
||||||
|
this.finalColor = finalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw( Graphics2D graphics ) {
|
||||||
|
if( isActive() && this.color != null ) {
|
||||||
|
graphics.setColor(this.color.getJavaColor());
|
||||||
|
graphics.drawLine((int) position.x - 3, (int) position.y - 3, (int) position.x + 3, (int) position.y + 3);
|
||||||
|
graphics.drawLine((int) position.x + 3, (int) position.y - 3, (int) position.x - 3, (int) position.y + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,36 +1,158 @@
|
|||||||
package schule.ngb.zm.particles;
|
package schule.ngb.zm.particles;
|
||||||
|
|
||||||
import schule.ngb.zm.Zeichenmaschine;
|
import schule.ngb.zm.*;
|
||||||
import schule.ngb.zm.layers.DrawableLayer;
|
import schule.ngb.zm.layers.DrawableLayer;
|
||||||
|
import schule.ngb.zm.layers.DrawingLayer;
|
||||||
|
|
||||||
public class ParticleExample extends Zeichenmaschine {
|
import java.awt.Graphics2D;
|
||||||
|
|
||||||
|
public class ParticleExample extends Testmaschine {
|
||||||
|
|
||||||
public static void main( String[] args ) {
|
public static void main( String[] args ) {
|
||||||
new ParticleExample();
|
new ParticleExample();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParticleExample() {
|
public ParticleExample() {
|
||||||
super(400, 400, "ZM: Particles");
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
ParticleEmitter pgen;
|
ParticleEmitter pe1, pe2, pe3;
|
||||||
|
|
||||||
|
Rocket r;
|
||||||
|
|
||||||
public void setup() {
|
public void setup() {
|
||||||
pgen = new ParticleEmitter(
|
getLayer(DrawingLayer.class).hide();
|
||||||
200, 200, 1,
|
background.setColor(0);
|
||||||
new BasicParticleFactory()
|
drawing.noStroke();
|
||||||
|
drawing.setFillColor(WHITE);
|
||||||
|
for( int i = 0; i < 1000; i++ ) {
|
||||||
|
drawing.point(random(0, canvasWidth), random(0, canvasHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
pe1 = new ParticleEmitter(
|
||||||
|
100, 100, 50, 5,
|
||||||
|
// new BasicParticleFactory(PINK, BLUE)
|
||||||
|
new GenericParticleFactory<Particle>(() -> {
|
||||||
|
return new Particle() {
|
||||||
|
@Override
|
||||||
|
public void draw( Graphics2D graphics ) {
|
||||||
|
graphics.setColor(Color.MAGENTA.getJavaColor());
|
||||||
|
graphics.rotate(Constants.radians(45), (int) position.x, (int) position.y);
|
||||||
|
graphics.drawRect((int) position.x - 3, (int) position.y - 3, 6, 6);
|
||||||
|
graphics.rotate(Constants.radians(-45), (int) position.x, (int) position.y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
);
|
);
|
||||||
pgen.randomness = .5;
|
pe1.randomness = .2;
|
||||||
pgen.angle = 45;
|
pe1.angle = 45;
|
||||||
|
pe1.strength = 200;
|
||||||
|
|
||||||
|
|
||||||
|
pe2 = new ParticleEmitter(
|
||||||
|
300, 300, 50, 10,
|
||||||
|
new GenericParticleFactory(() -> new StarParticle(RED, new Color(BLUE, 55)))
|
||||||
|
//new GenericParticleFactory(StarParticle.class, RED, new Color(BLUE, 55))
|
||||||
|
);
|
||||||
|
pe2.direction = NORTH.asVector().scale(100);
|
||||||
|
pe2.randomness = .8;
|
||||||
|
pe2.angle = 90;
|
||||||
|
pe2.strength = 200;
|
||||||
|
|
||||||
|
|
||||||
|
pe3 = new ParticleEmitter(
|
||||||
|
100, 400, 20, 8,
|
||||||
|
new BasicParticleFactory(YELLOW, RED)
|
||||||
|
);
|
||||||
|
pe3.direction = SOUTH.asVector();
|
||||||
|
pe3.randomness = .33;
|
||||||
|
pe3.angle = 30;
|
||||||
|
|
||||||
DrawableLayer drawables = new DrawableLayer();
|
DrawableLayer drawables = new DrawableLayer();
|
||||||
addLayer(drawables);
|
addLayer(drawables);
|
||||||
drawables.add(pgen);
|
drawables.add(pe1, pe2, pe3);
|
||||||
pgen.start();
|
|
||||||
|
pe1.start();
|
||||||
|
pe2.start();
|
||||||
|
pe3.start();
|
||||||
|
|
||||||
|
r = new Rocket(200, 400);
|
||||||
|
drawables.add(r);
|
||||||
|
r.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update( double delta ) {
|
public void update( double delta ) {
|
||||||
pgen.update(delta);
|
pe1.update(delta);
|
||||||
|
pe2.update(delta);
|
||||||
|
pe3.update(delta);
|
||||||
|
pe3.position.add(NORTH.asVector().scale(Constants.map(runtime, 0, 1000, 0, 10) * delta));
|
||||||
|
|
||||||
|
if( r.isActive() ) {
|
||||||
|
r.update(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rocket extends PhysicsObject implements Drawable {
|
||||||
|
|
||||||
|
ParticleEmitter trail;
|
||||||
|
|
||||||
|
private boolean starting = false;
|
||||||
|
|
||||||
|
private double acc = 4;
|
||||||
|
|
||||||
|
public Rocket( double x, double y ) {
|
||||||
|
super(new Vector(x, y));
|
||||||
|
trail = new ParticleEmitter(
|
||||||
|
x, y, 30, 6,
|
||||||
|
new BasicParticleFactory(YELLOW, RED)
|
||||||
|
);
|
||||||
|
trail.direction = SOUTH.asVector();
|
||||||
|
trail.randomness = .33;
|
||||||
|
trail.angle = 30;
|
||||||
|
trail.position = this.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
starting = true;
|
||||||
|
trail.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return starting;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVisible() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update( double delta ) {
|
||||||
|
super.update(delta);
|
||||||
|
if( this.acceleration.lengthSq() < acc * acc ) {
|
||||||
|
this.accelerate(NORTHWEST.asVector().scale(acc));
|
||||||
|
}
|
||||||
|
|
||||||
|
trail.update(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw( Graphics2D graphics ) {
|
||||||
|
graphics.rotate(-Constants.radians(velocity.angle() + 180), position.getIntX(), position.getIntY());
|
||||||
|
trail.draw(graphics);
|
||||||
|
|
||||||
|
graphics.setColor(WHITE.getJavaColor());
|
||||||
|
graphics.fillRect(position.getIntX() - 6, position.getIntY() - 32, 12, 32);
|
||||||
|
graphics.fillPolygon(
|
||||||
|
new int[]{position.getIntX() - 6, position.getIntX(), position.getIntX() + 6},
|
||||||
|
new int[]{position.getIntY() - 32, position.getIntY() - 40, position.getIntY() - 32},
|
||||||
|
3
|
||||||
|
);
|
||||||
|
graphics.rotate(Constants.radians(velocity.angle() + 180), position.getIntX(), position.getIntY());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user