mirror of
https://github.com/Noratrieb/Java2DGame.git
synced 2026-01-14 14:05:01 +01:00
big update, stonks
This commit is contained in:
parent
9f460d499a
commit
8e15ad3ac8
26 changed files with 200 additions and 49 deletions
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
|
|
@ -8,7 +8,7 @@
|
|||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_15" default="false" project-jdk-name="15" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_14" project-jdk-name="14" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
2
2DGame.iml
Normal file
2
2DGame.iml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4" />
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import core.Master;
|
||||
import core.general.Master;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
|
@ -24,7 +24,8 @@ class Main extends JFrame {
|
|||
add(master);
|
||||
|
||||
setTitle("Points");
|
||||
setSize(1000, (int) (1000 / Master.SCREEN_RATIO));
|
||||
int w = 1500;
|
||||
setSize(w, (int) (w / Master.SCREEN_RATIO));
|
||||
setLocationRelativeTo(null);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package core;
|
||||
package core.general;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
package core;
|
||||
package core.general;
|
||||
|
||||
import core.math.Vector2D;
|
||||
import core.objects.core.CollGameObject;
|
||||
import core.physics.Collidable;
|
||||
import core.physics.Collision;
|
||||
import core.objects.base.DebugPos;
|
||||
import core.physics.hitboxes.Hitbox;
|
||||
import objects.ships.BattleShip;
|
||||
import core.objects.core.GameObject;
|
||||
import objects.ships.Submarine;
|
||||
import objects.ships.Turret;
|
||||
import objects.world.Grid;
|
||||
import objects.world.Wall;
|
||||
|
||||
|
|
@ -45,6 +48,7 @@ public class Master extends JPanel {
|
|||
*/
|
||||
private final ArrayList<ArrayList<Drawable>> drawables;
|
||||
|
||||
|
||||
/**
|
||||
* All physics objects that exist
|
||||
*/
|
||||
|
|
@ -55,6 +59,11 @@ public class Master extends JPanel {
|
|||
*/
|
||||
private final ArrayList<GameObject> objectBuffer;
|
||||
|
||||
/**
|
||||
* All physics objects that exist
|
||||
*/
|
||||
private final ArrayList<Collidable> collidablesBuffer;
|
||||
|
||||
/**
|
||||
* Whether the left mouse button has been pressed since the last frame
|
||||
*/
|
||||
|
|
@ -74,6 +83,7 @@ public class Master extends JPanel {
|
|||
objects = new ArrayList<>();
|
||||
objectBuffer = new ArrayList<>();
|
||||
collidables = new ArrayList<>();
|
||||
collidablesBuffer = new ArrayList<>();
|
||||
drawables = new ArrayList<>();
|
||||
drawables.add(new ArrayList<>());
|
||||
|
||||
|
|
@ -82,9 +92,11 @@ public class Master extends JPanel {
|
|||
|
||||
BattleShip battleShip = new BattleShip(Color.DARK_GRAY);
|
||||
BattleShip bs = new BattleShip(140, 10, 10, 80, Color.GREEN);
|
||||
/*for (int i = 0; i < 10; i++) {
|
||||
bs.addTurret(new Turret(bs, 25, 10 * i + 1, 50, i % 5));
|
||||
}*/
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
bs.addTurret(new Turret(bs, 2.5, 10 * i + 1, 5, (i % 5 )+ 1));
|
||||
|
||||
}
|
||||
create(bs);
|
||||
create(battleShip);
|
||||
|
||||
|
|
@ -143,11 +155,16 @@ public class Master extends JPanel {
|
|||
* This method is the entry method for each frame. It handles everything about the frame
|
||||
*/
|
||||
public void refresh() {
|
||||
long time = System.currentTimeMillis();
|
||||
objects.clear();
|
||||
objects.addAll(objectBuffer);
|
||||
objectBuffer.clear();
|
||||
objects.forEach(GameObject::update);
|
||||
collidables.clear();
|
||||
collidables.addAll(collidablesBuffer);
|
||||
objects.forEach(GameObject::startUpdate);
|
||||
long time2 = System.currentTimeMillis();
|
||||
mousePressed = false;
|
||||
repaint();
|
||||
System.out.println("Frame took " + (System.currentTimeMillis() - time) + "ms, " + (time2 - time) + "ms for update, " + (System.currentTimeMillis() - time2) + "ms for draw");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -182,8 +199,8 @@ public class Master extends JPanel {
|
|||
*/
|
||||
public void create(GameObject obj, int renderLayer) {
|
||||
objectBuffer.add(obj);
|
||||
if (obj instanceof Collidable) {
|
||||
collidables.add((Collidable) obj);
|
||||
if (obj instanceof CollGameObject) {
|
||||
collidablesBuffer.add((Collidable) obj);
|
||||
}
|
||||
addDrawable(obj, renderLayer);
|
||||
|
||||
|
|
@ -221,15 +238,20 @@ public class Master extends JPanel {
|
|||
* @return True if it collides with something, false if it doesn't - Should return a Collision
|
||||
*/
|
||||
public Collision doesCollide(Collidable collidable) {
|
||||
long time = System.nanoTime();
|
||||
Collision collides = null;
|
||||
|
||||
for (Collidable other : collidables) {
|
||||
double distance = Vector2D.distance(other.getCenterPos(), collidable.getCenterPos());
|
||||
|
||||
if (!collidable.getIgnores().contains(other.getClass()) && !other.isTrigger()) { //ONLY calculate when it's not ignored
|
||||
|
||||
double distance = Vector2D.distance(other.getCenterPos(), collidable.getCenterPos());
|
||||
if (other != collidable && (distance < other.getHitbox().getSize() + collidable.getHitbox().getSize())) {
|
||||
|
||||
if (other.getHitbox().collidesWith(collidable.getHitbox())) {
|
||||
collides = new Collision(collidable, other);
|
||||
collidable.onCollision();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -245,11 +267,11 @@ public class Master extends JPanel {
|
|||
}
|
||||
|
||||
public void destroy(GameObject gameObject) {
|
||||
objects.remove(gameObject);
|
||||
objectBuffer.remove(gameObject);
|
||||
drawables.get(gameObject.getLayer()).remove(gameObject);
|
||||
if (gameObject instanceof Collidable) {
|
||||
collidables.remove(gameObject);
|
||||
collidablesBuffer.remove(gameObject);
|
||||
drawables.get(Hitbox.HITBOX_RENDER_LAYER).remove(((CollGameObject) gameObject).getHitbox());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package core.math;
|
||||
|
||||
import core.Master;
|
||||
import core.math.Vector2D;
|
||||
import core.general.Master;
|
||||
|
||||
/**
|
||||
* This class provides everything about the local coordinate system the game uses
|
||||
|
|
@ -24,6 +23,7 @@ public class Coords {
|
|||
|
||||
/**
|
||||
* Get the world coordinates of a point in map coordinates
|
||||
*
|
||||
* @param value A point in map coordinates
|
||||
* @return The point in world coordinates
|
||||
*/
|
||||
|
|
@ -44,4 +44,10 @@ public class Coords {
|
|||
double y = (value.y / master.getH()) * Master.SCREEN_Y_COORDINATES;
|
||||
return new Vector2D(x, y);
|
||||
}
|
||||
|
||||
public static boolean outOfBounds(Vector2D position, Vector2D size) {
|
||||
|
||||
return (position.x + size.magnitude() < 0 || position.x - size.magnitude() > Master.SCREEN_Y_COORDINATES * Master.SCREEN_RATIO ||
|
||||
position.y + size.magnitude() < 0 || position.y - position.magnitude() > Master.SCREEN_Y_COORDINATES);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ package core.math;
|
|||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* A 2-dimensional Vector that can be used to store position or velocity
|
||||
* A 2-dimensional Vector that can be used to store position or velocity or size or whatever
|
||||
*/
|
||||
public class Vector2D {
|
||||
|
||||
|
|
@ -44,6 +44,7 @@ public class Vector2D {
|
|||
return new Vector2D(point.x, point.y);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add another Vector to this vector, modifies this object
|
||||
*
|
||||
|
|
@ -123,6 +124,17 @@ public class Vector2D {
|
|||
return new Vector2D(a.x - b.x, a.y - b.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Divide a Vector by a scalar
|
||||
*
|
||||
* @param a Vector a
|
||||
* @param b Scalar b
|
||||
* @return The result
|
||||
*/
|
||||
public static Vector2D divideS(Vector2D a, double b) {
|
||||
return new Vector2D(a.x / b, a.y / b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate a point around another point
|
||||
* <p>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package core.objects.base;
|
||||
|
||||
import core.math.Coords;
|
||||
import core.math.Vector2D;
|
||||
import core.objects.core.GameObject;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,16 @@ import core.objects.core.GameObject;
|
|||
import core.physics.Collidable;
|
||||
import core.physics.hitboxes.Hitbox;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A specialization of GameObject with Collidable properties
|
||||
*/
|
||||
public abstract class CollGameObject extends GameObject implements Collidable {
|
||||
|
||||
protected Hitbox hitbox;
|
||||
protected ArrayList<Class<?>> ignores = new ArrayList<>();
|
||||
protected boolean isTrigger = false;
|
||||
|
||||
public CollGameObject(Vector2D position, Vector2D size, Hitbox hitbox) {
|
||||
super(position, size);
|
||||
|
|
@ -54,4 +58,18 @@ public abstract class CollGameObject extends GameObject implements Collidable {
|
|||
public Vector2D getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCollision() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Class<?>> getIgnores() {
|
||||
return ignores;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrigger() {
|
||||
return isTrigger;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
package core.objects.core;
|
||||
|
||||
import core.math.Coords;
|
||||
import core.Drawable;
|
||||
import core.Master;
|
||||
import core.general.Drawable;
|
||||
import core.general.Master;
|
||||
import core.math.Vector2D;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* The GameObject class is the superclass of every GameObject that can be displayed on screen. It has the 2
|
||||
* {@link #update()} and {@link #draw(Graphics2D)} methods that have to be
|
||||
* {@link #update()} and {@link #draw(Graphics2D)} methods that have to be overridden
|
||||
*/
|
||||
public abstract class GameObject implements Drawable {
|
||||
|
||||
protected boolean doesDespawn = true;
|
||||
|
||||
protected Vector2D position;
|
||||
protected Vector2D size;
|
||||
|
||||
|
|
@ -37,6 +39,12 @@ public abstract class GameObject implements Drawable {
|
|||
this.layer = 0;
|
||||
}
|
||||
|
||||
public void startUpdate(){
|
||||
if(Coords.outOfBounds(position, size)){
|
||||
destroy();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>The update method is called every frame before the {@link #draw(Graphics2D)} method by the master object on each object. Everything
|
||||
|
|
@ -99,7 +107,12 @@ public abstract class GameObject implements Drawable {
|
|||
|
||||
Vector2D abs;
|
||||
|
||||
abs = (arg.contains("center")) ? Coords.getWorldCoords(getCenterPosition()) : Coords.getWorldCoords(position);
|
||||
if(arg.contains("center")){
|
||||
abs = Coords.getWorldCoords(new Vector2D(position.x - size.x / 2, position.y - size.y / 2));
|
||||
} else {
|
||||
abs = Coords.getWorldCoords(position);
|
||||
}
|
||||
|
||||
Vector2D sizeAbs = Coords.getWorldCoords(size);
|
||||
|
||||
g2d.setPaint(mainColor);
|
||||
|
|
@ -121,7 +134,6 @@ public abstract class GameObject implements Drawable {
|
|||
g2d.fillRoundRect((int) abs.x, (int) abs.y, (int) sizeAbs.x, (int) sizeAbs.y, arcW, arcH);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void destroy() {
|
||||
master.destroy(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,14 @@ package core.physics;
|
|||
import core.math.Vector2D;
|
||||
import core.physics.hitboxes.Hitbox;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public interface Collidable {
|
||||
|
||||
Hitbox getHitbox();
|
||||
Vector2D getCenterPos();
|
||||
Vector2D getSize();
|
||||
|
||||
void onCollision();
|
||||
ArrayList<Class<?>> getIgnores();
|
||||
boolean isTrigger();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package core.physics.hitboxes;
|
||||
|
||||
import core.Drawable;
|
||||
import core.general.Drawable;
|
||||
import core.math.Vector2D;
|
||||
import core.physics.Collision;
|
||||
|
||||
public abstract class Hitbox implements Drawable {
|
||||
|
||||
|
||||
public static final int HITBOX_RENDER_LAYER = 1;
|
||||
private final boolean isTrigger;
|
||||
|
||||
protected Hitbox(boolean isTrigger) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package core.physics.hitboxes;
|
||||
|
||||
import core.math.Coords;
|
||||
import core.Master;
|
||||
import core.general.Master;
|
||||
import core.math.Vector2D;
|
||||
|
||||
import java.awt.*;
|
||||
|
|
@ -31,7 +31,7 @@ public class RectHitBox extends Hitbox {
|
|||
this.x2 = Vector2D.add(x1, new Vector2D(size.x, 0));
|
||||
this.y1 = Vector2D.add(x1, new Vector2D(0, size.y));
|
||||
this.y2 = Vector2D.add(x1, new Vector2D(size.x, size.y));
|
||||
Master.getMaster().addDrawable(this, 1);
|
||||
Master.getMaster().addDrawable(this, HITBOX_RENDER_LAYER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -46,7 +46,6 @@ public class RectHitBox extends Hitbox {
|
|||
this.x2 = Vector2D.add(x1, new Vector2D(size.x, 0));
|
||||
this.y1 = Vector2D.add(x1, new Vector2D(0, size.y));
|
||||
this.y2 = Vector2D.add(x1, new Vector2D(size.x, size.y));
|
||||
Master.getMaster().addDrawable(this, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -166,10 +165,10 @@ public class RectHitBox extends Hitbox {
|
|||
return y2;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* @Override
|
||||
public String toString() {
|
||||
return "RectHitBox{" + x1 + " " + x2 + "\n" + y1 + " " + y2 + "}";
|
||||
}
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g2d) {
|
||||
|
|
@ -177,7 +176,7 @@ public class RectHitBox extends Hitbox {
|
|||
Vector2D abs = Coords.getWorldCoords(x1);
|
||||
Vector2D sizeAbs = Coords.getWorldCoords(Vector2D.subtract(y2, x1));
|
||||
|
||||
g2d.drawRect((int)abs.x, (int)abs.y, (int)sizeAbs.x, (int)sizeAbs.y);
|
||||
g2d.setPaint(Color.MAGENTA);
|
||||
g2d.drawRect((int)abs.x, (int)abs.y, (int)sizeAbs.x, (int)sizeAbs.y);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
67
src/main/java/objects/GameObject.java
Normal file
67
src/main/java/objects/GameObject.java
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package objects;
|
||||
|
||||
import core.general.Master;
|
||||
import core.math.Vector2D;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* The GameObject class is the superclass of every GameObject that can be displayed on screen. It has the 2
|
||||
* {@link #update(Master)} and {@link #draw(Graphics2D, int, Master)} methods that have to be
|
||||
*/
|
||||
public abstract class GameObject {
|
||||
|
||||
protected int w;
|
||||
protected int h;
|
||||
|
||||
protected Vector2D position;
|
||||
protected Vector2D size;
|
||||
|
||||
protected Vector2D velocity;
|
||||
|
||||
public GameObject(double x, double y, double xSize, double ySize) {
|
||||
this(new Vector2D(x, y), new Vector2D(xSize, ySize));
|
||||
}
|
||||
|
||||
public GameObject(Vector2D position, Vector2D size) {
|
||||
this.position = position;
|
||||
this.size = size;
|
||||
this.velocity = new Vector2D();
|
||||
}
|
||||
|
||||
public abstract void draw(Graphics2D g2d, int w, Master master);
|
||||
public abstract void update(Master master);
|
||||
|
||||
|
||||
public double getMapCoords(double value, boolean isX){
|
||||
if (isX){
|
||||
return (position.x + value / 100 * size.x);
|
||||
} else {
|
||||
return (position.y + value / 100 * size.y);
|
||||
}
|
||||
}
|
||||
|
||||
public double getMapCoordsSize(double value, boolean useX){
|
||||
if (useX){
|
||||
return (value / 100 * size.x);
|
||||
} else {
|
||||
return (value / 100 * size.y);
|
||||
}
|
||||
}
|
||||
|
||||
public double getWorldCoords(double value, boolean isX){
|
||||
if (isX){
|
||||
return (value / 100 * w);
|
||||
} else {
|
||||
return (value / 100 * h);
|
||||
}
|
||||
}
|
||||
|
||||
public int getWorldCoordsFromLocal(double value, boolean isX){
|
||||
return (int) getWorldCoords(getMapCoords(value, isX), isX);
|
||||
}
|
||||
|
||||
public int getWorldCoordsFromLocalSize(double value, boolean useX){
|
||||
return (int) getWorldCoords(getMapCoordsSize(value, useX), useX);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,10 +14,9 @@ public class BattleShip extends GameObject {
|
|||
|
||||
public BattleShip(Color mainColor) {
|
||||
this(20, 20, 10, 40, mainColor);
|
||||
//TODO turret size should use w and h but correct just like with world coords
|
||||
turrets.add(new Turret(this, 2.5, 10, 5, 3));
|
||||
//turrets.add(new Turret(this, 25, 10, 50, 2));
|
||||
//turrets.add(new Turret(this, 25, 70, 50, 2));
|
||||
turrets.add(new Turret(this, 2.5, 7, 5, 3));
|
||||
turrets.add(new Turret(this, 2.5, 15, 5, 3));
|
||||
turrets.add(new Turret(this, 2.5, 25, 5, 3));
|
||||
}
|
||||
|
||||
public BattleShip(double x, double y, double xSize, double ySize, Color mainColor) {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,23 @@
|
|||
package objects.ships;
|
||||
|
||||
import core.math.Vector2D;
|
||||
import core.objects.core.GameObject;
|
||||
import core.objects.core.CollGameObject;
|
||||
import core.physics.hitboxes.RectHitBox;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* A shell fired by a cannon
|
||||
*/
|
||||
public class Shell extends GameObject {
|
||||
public class Shell extends CollGameObject {
|
||||
|
||||
|
||||
public Shell(Vector2D position, Vector2D size, Vector2D velocity) {
|
||||
super(position, size/*, new RectHitBox(position, size)*/);
|
||||
super(position, size, new RectHitBox(position, size));
|
||||
this.velocity = velocity;
|
||||
this.mainColor = Color.ORANGE;
|
||||
this.ignores.add(Shell.class);
|
||||
this.isTrigger = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -26,4 +29,10 @@ public class Shell extends GameObject {
|
|||
public void update() {
|
||||
moveTo(Vector2D.add(position, velocity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCollision() {
|
||||
destroy();
|
||||
//master.debugPos(position);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,5 +27,4 @@ public class Submarine extends CollGameObject {
|
|||
Vector2D centerRelPos = new Vector2D(relPos.x - size.x/2, relPos.y - size.y/2);
|
||||
moveTo(centerRelPos);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,8 +81,8 @@ public class Turret extends GameObject {
|
|||
|
||||
Point msLoc = master.getMouseLocation();
|
||||
Vector2D mouseRel = Coords.getMapCoordsFromWorld(Vector2D.fromPoint(msLoc)); //100 correct
|
||||
Vector2D center = battleShip.getMapCoords(getCenterPosition());
|
||||
double targetRotation = -Math.atan2(center.x - mouseRel.x, center.y - mouseRel.y);
|
||||
Vector2D centerMap = battleShip.getMapCoords(getCenterPosition());
|
||||
double targetRotation = -Math.atan2(centerMap.x - mouseRel.x, centerMap.y - mouseRel.y);
|
||||
|
||||
rotation = ExMath.angleLerp(rotation, targetRotation, ROTATION_SPEED);
|
||||
|
||||
|
|
@ -98,14 +98,13 @@ public class Turret extends GameObject {
|
|||
lastShot = System.currentTimeMillis();
|
||||
|
||||
Vector2D shellVel = Vector2D.getUnitVector(rotation).negative().multiply(SHELL_SPEED);
|
||||
master.debugPos(battleShip.getMapCoords(center));
|
||||
master.debugPos(center);
|
||||
//master.debugPos(centerMap);
|
||||
Vector2D pos = Vector2D.rotateAround(
|
||||
battleShip.getMapCoords(center),
|
||||
centerMap,
|
||||
spawnPosNR,
|
||||
rotation);
|
||||
|
||||
master.debugPos(pos);
|
||||
//master.debugPos(pos);
|
||||
master.create(new Shell(pos, new Vector2D(SHELL_SIZE, SHELL_SIZE), shellVel));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue