Added better model shading, and visually stretched bullets for fancier appearance.
This commit is contained in:
parent
4e0e1a32fa
commit
90c9ae22e8
|
@ -3,6 +3,7 @@ package nl.andrewl.aos2_client.model;
|
||||||
import nl.andrewl.aos2_client.Camera;
|
import nl.andrewl.aos2_client.Camera;
|
||||||
import nl.andrewl.aos_core.model.Player;
|
import nl.andrewl.aos_core.model.Player;
|
||||||
import nl.andrewl.aos_core.model.item.Inventory;
|
import nl.andrewl.aos_core.model.item.Inventory;
|
||||||
|
import org.joml.Matrix3f;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -14,6 +15,9 @@ public class ClientPlayer extends Player {
|
||||||
private final Matrix4f heldItemTransform = new Matrix4f();
|
private final Matrix4f heldItemTransform = new Matrix4f();
|
||||||
private final float[] heldItemTransformData = new float[16];
|
private final float[] heldItemTransformData = new float[16];
|
||||||
|
|
||||||
|
private final Matrix3f heldItemNormalTransform = new Matrix3f();
|
||||||
|
private final float[] heldItemNormalTransformData = new float[9];
|
||||||
|
|
||||||
public ClientPlayer(int id, String username) {
|
public ClientPlayer(int id, String username) {
|
||||||
super(id, username);
|
super(id, username);
|
||||||
this.health = 1;
|
this.health = 1;
|
||||||
|
@ -45,9 +49,16 @@ public class ClientPlayer extends Player {
|
||||||
.rotate(-cam.getOrientation().y + (float) Math.PI / 2, Camera.RIGHT)
|
.rotate(-cam.getOrientation().y + (float) Math.PI / 2, Camera.RIGHT)
|
||||||
.translate(-0.35f, -0.4f, 0.5f);
|
.translate(-0.35f, -0.4f, 0.5f);
|
||||||
heldItemTransform.get(heldItemTransformData);
|
heldItemTransform.get(heldItemTransformData);
|
||||||
|
|
||||||
|
heldItemTransform.normal(heldItemNormalTransform);
|
||||||
|
heldItemNormalTransform.get(heldItemNormalTransformData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float[] getHeldItemTransformData() {
|
public float[] getHeldItemTransformData() {
|
||||||
return heldItemTransformData;
|
return heldItemTransformData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float[] getHeldItemNormalTransformData() {
|
||||||
|
return heldItemNormalTransformData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package nl.andrewl.aos2_client.model;
|
||||||
import nl.andrewl.aos2_client.Camera;
|
import nl.andrewl.aos2_client.Camera;
|
||||||
import nl.andrewl.aos_core.model.Player;
|
import nl.andrewl.aos_core.model.Player;
|
||||||
import nl.andrewl.aos_core.model.item.ItemTypes;
|
import nl.andrewl.aos_core.model.item.ItemTypes;
|
||||||
|
import org.joml.Matrix3f;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
|
@ -22,14 +23,15 @@ public class OtherPlayer extends Player {
|
||||||
*/
|
*/
|
||||||
private byte selectedBlockValue;
|
private byte selectedBlockValue;
|
||||||
|
|
||||||
/**
|
|
||||||
* The transformation used to render this player in the world.
|
|
||||||
*/
|
|
||||||
private final Matrix4f modelTransform = new Matrix4f();
|
private final Matrix4f modelTransform = new Matrix4f();
|
||||||
private final float[] modelTransformData = new float[16];
|
private final float[] modelTransformData = new float[16];
|
||||||
|
private final Matrix3f normalTransform = new Matrix3f();
|
||||||
|
private final float[] normalTransformData = new float[9];
|
||||||
|
|
||||||
private final Matrix4f heldItemTransform = new Matrix4f();
|
private final Matrix4f heldItemTransform = new Matrix4f();
|
||||||
private final float[] heldItemTransformData = new float[16];
|
private final float[] heldItemTransformData = new float[16];
|
||||||
|
private final Matrix3f heldItemNormalTransform = new Matrix3f();
|
||||||
|
private final float[] heldItemNormalTransformData = new float[9];
|
||||||
|
|
||||||
public OtherPlayer(int id, String username) {
|
public OtherPlayer(int id, String username) {
|
||||||
super(id, username);
|
super(id, username);
|
||||||
|
@ -61,17 +63,32 @@ public class OtherPlayer extends Player {
|
||||||
.translate(position)
|
.translate(position)
|
||||||
.rotate(orientation.x, Camera.UP);
|
.rotate(orientation.x, Camera.UP);
|
||||||
modelTransform.get(modelTransformData);
|
modelTransform.get(modelTransformData);
|
||||||
|
|
||||||
|
modelTransform.normal(normalTransform);
|
||||||
|
normalTransform.get(normalTransformData);
|
||||||
|
|
||||||
heldItemTransform.set(modelTransform)
|
heldItemTransform.set(modelTransform)
|
||||||
.translate(0.5f, 1.1f, -0.5f)
|
.translate(0.5f, 1.1f, -0.5f)
|
||||||
.rotate((float) Math.PI, Camera.UP);
|
.rotate((float) Math.PI, Camera.UP);
|
||||||
heldItemTransform.get(heldItemTransformData);
|
heldItemTransform.get(heldItemTransformData);
|
||||||
|
|
||||||
|
heldItemTransform.normal(heldItemNormalTransform);
|
||||||
|
heldItemNormalTransform.get(heldItemNormalTransformData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float[] getModelTransformData() {
|
public float[] getModelTransformData() {
|
||||||
return modelTransformData;
|
return modelTransformData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float[] getNormalTransformData() {
|
||||||
|
return normalTransformData;
|
||||||
|
}
|
||||||
|
|
||||||
public float[] getHeldItemTransformData() {
|
public float[] getHeldItemTransformData() {
|
||||||
return heldItemTransformData;
|
return heldItemTransformData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float[] getHeldItemNormalTransformData() {
|
||||||
|
return heldItemNormalTransformData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package nl.andrewl.aos2_client.render;
|
||||||
import nl.andrewl.aos2_client.Camera;
|
import nl.andrewl.aos2_client.Camera;
|
||||||
import nl.andrewl.aos2_client.Client;
|
import nl.andrewl.aos2_client.Client;
|
||||||
import nl.andrewl.aos2_client.config.ClientConfig;
|
import nl.andrewl.aos2_client.config.ClientConfig;
|
||||||
|
import nl.andrewl.aos2_client.model.ClientPlayer;
|
||||||
import nl.andrewl.aos2_client.render.chunk.ChunkRenderer;
|
import nl.andrewl.aos2_client.render.chunk.ChunkRenderer;
|
||||||
import nl.andrewl.aos2_client.render.gui.GUIRenderer;
|
import nl.andrewl.aos2_client.render.gui.GUIRenderer;
|
||||||
import nl.andrewl.aos2_client.render.gui.GUITexture;
|
import nl.andrewl.aos2_client.render.gui.GUITexture;
|
||||||
|
@ -11,10 +12,12 @@ import nl.andrewl.aos_core.model.item.BlockItemStack;
|
||||||
import nl.andrewl.aos_core.model.item.Gun;
|
import nl.andrewl.aos_core.model.item.Gun;
|
||||||
import nl.andrewl.aos_core.model.item.GunItemStack;
|
import nl.andrewl.aos_core.model.item.GunItemStack;
|
||||||
import nl.andrewl.aos_core.model.item.ItemTypes;
|
import nl.andrewl.aos_core.model.item.ItemTypes;
|
||||||
|
import org.joml.Matrix3f;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
import org.lwjgl.glfw.*;
|
import org.lwjgl.glfw.*;
|
||||||
import org.lwjgl.opengl.GL;
|
import org.lwjgl.opengl.GL;
|
||||||
|
import org.lwjgl.opengl.GLUtil;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -184,9 +187,11 @@ public class GameRenderer {
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
chunkRenderer.draw(camera, client.getWorld().getChunkMeshesToDraw());
|
chunkRenderer.draw(camera, client.getWorld().getChunkMeshesToDraw());
|
||||||
|
|
||||||
|
ClientPlayer myPlayer = client.getMyPlayer();
|
||||||
|
|
||||||
// Draw models. Use one texture at a time for efficiency.
|
// Draw models. Use one texture at a time for efficiency.
|
||||||
modelRenderer.start(camera.getViewTransformData());
|
modelRenderer.start(camera.getViewTransformData());
|
||||||
client.getMyPlayer().updateHeldItemTransform(camera);
|
myPlayer.updateHeldItemTransform(camera);
|
||||||
|
|
||||||
playerModel.bind();
|
playerModel.bind();
|
||||||
for (var player : client.getPlayers().values()) {
|
for (var player : client.getPlayers().values()) {
|
||||||
|
@ -195,17 +200,17 @@ public class GameRenderer {
|
||||||
} else {
|
} else {
|
||||||
modelRenderer.setAspectColor(new Vector3f(0.3f, 0.3f, 0.3f));
|
modelRenderer.setAspectColor(new Vector3f(0.3f, 0.3f, 0.3f));
|
||||||
}
|
}
|
||||||
modelRenderer.render(playerModel, player.getModelTransformData());
|
modelRenderer.render(playerModel, player.getModelTransformData(), player.getNormalTransformData());
|
||||||
}
|
}
|
||||||
playerModel.unbind();
|
playerModel.unbind();
|
||||||
|
|
||||||
rifleModel.bind();
|
rifleModel.bind();
|
||||||
if (client.getMyPlayer().getInventory().getSelectedItemStack().getType().getId() == ItemTypes.RIFLE.getId()) {
|
if (myPlayer.getInventory().getSelectedItemStack().getType().getId() == ItemTypes.RIFLE.getId()) {
|
||||||
modelRenderer.render(rifleModel, client.getMyPlayer().getHeldItemTransformData());
|
modelRenderer.render(rifleModel, myPlayer.getHeldItemTransformData(), myPlayer.getHeldItemNormalTransformData());
|
||||||
}
|
}
|
||||||
for (var player : client.getPlayers().values()) {
|
for (var player : client.getPlayers().values()) {
|
||||||
if (player.getHeldItemId() == ItemTypes.RIFLE.getId()) {
|
if (player.getHeldItemId() == ItemTypes.RIFLE.getId()) {
|
||||||
modelRenderer.render(rifleModel, player.getHeldItemTransformData());
|
modelRenderer.render(rifleModel, player.getHeldItemTransformData(), player.getHeldItemNormalTransformData());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rifleModel.unbind();
|
rifleModel.unbind();
|
||||||
|
@ -214,23 +219,26 @@ public class GameRenderer {
|
||||||
if (client.getMyPlayer().getInventory().getSelectedItemStack().getType().getId() == ItemTypes.BLOCK.getId()) {
|
if (client.getMyPlayer().getInventory().getSelectedItemStack().getType().getId() == ItemTypes.BLOCK.getId()) {
|
||||||
BlockItemStack stack = (BlockItemStack) client.getMyPlayer().getInventory().getSelectedItemStack();
|
BlockItemStack stack = (BlockItemStack) client.getMyPlayer().getInventory().getSelectedItemStack();
|
||||||
modelRenderer.setAspectColor(client.getWorld().getPalette().getColor(stack.getSelectedValue()));
|
modelRenderer.setAspectColor(client.getWorld().getPalette().getColor(stack.getSelectedValue()));
|
||||||
modelRenderer.render(blockModel, client.getMyPlayer().getHeldItemTransformData());
|
modelRenderer.render(blockModel, myPlayer.getHeldItemTransformData(), myPlayer.getHeldItemNormalTransformData());
|
||||||
}
|
}
|
||||||
modelRenderer.setAspectColor(new Vector3f(0.5f, 0.5f, 0.5f));
|
modelRenderer.setAspectColor(new Vector3f(0.5f, 0.5f, 0.5f));
|
||||||
for (var player : client.getPlayers().values()) {
|
for (var player : client.getPlayers().values()) {
|
||||||
if (player.getHeldItemId() == ItemTypes.BLOCK.getId()) {
|
if (player.getHeldItemId() == ItemTypes.BLOCK.getId()) {
|
||||||
modelRenderer.render(blockModel, player.getHeldItemTransformData());
|
modelRenderer.render(blockModel, player.getHeldItemTransformData(), player.getHeldItemNormalTransformData());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
blockModel.unbind();
|
blockModel.unbind();
|
||||||
|
|
||||||
bulletModel.bind();
|
bulletModel.bind();
|
||||||
Matrix4f projectileTransform = new Matrix4f();
|
Matrix4f projectileTransform = new Matrix4f();
|
||||||
|
Matrix3f projectileNormalTransform = new Matrix3f();
|
||||||
for (var projectile : client.getProjectiles().values()) {
|
for (var projectile : client.getProjectiles().values()) {
|
||||||
projectileTransform.identity()
|
projectileTransform.identity()
|
||||||
.translate(projectile.getPosition())
|
.translate(projectile.getPosition())
|
||||||
.rotateTowards(projectile.getVelocity(), Camera.UP);
|
.rotateTowards(projectile.getVelocity(), Camera.UP)
|
||||||
modelRenderer.render(bulletModel, projectileTransform);
|
.scale(1, 1, projectile.getVelocity().length() / 5);
|
||||||
|
projectileTransform.normal(projectileNormalTransform);
|
||||||
|
modelRenderer.render(bulletModel, projectileTransform, projectileNormalTransform);
|
||||||
}
|
}
|
||||||
bulletModel.unbind();
|
bulletModel.unbind();
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package nl.andrewl.aos2_client.render;
|
package nl.andrewl.aos2_client.render;
|
||||||
|
|
||||||
import nl.andrewl.aos2_client.render.model.Model;
|
import nl.andrewl.aos2_client.render.model.Model;
|
||||||
|
import org.joml.Matrix3f;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ public class ModelRenderer {
|
||||||
private final int projectionUniform;
|
private final int projectionUniform;
|
||||||
private final int viewUniform;
|
private final int viewUniform;
|
||||||
private final int modelUniform;
|
private final int modelUniform;
|
||||||
|
private final int normalUniform;
|
||||||
private final int textureSamplerUniform;
|
private final int textureSamplerUniform;
|
||||||
private final int colorUniform;
|
private final int colorUniform;
|
||||||
|
|
||||||
|
@ -22,10 +24,11 @@ public class ModelRenderer {
|
||||||
.withShader("shader/model/vertex.glsl", GL_VERTEX_SHADER)
|
.withShader("shader/model/vertex.glsl", GL_VERTEX_SHADER)
|
||||||
.withShader("shader/model/fragment.glsl", GL_FRAGMENT_SHADER)
|
.withShader("shader/model/fragment.glsl", GL_FRAGMENT_SHADER)
|
||||||
.build();
|
.build();
|
||||||
// System.out.println(glGetProgramInfoLog(shaderProgram.getId())); // Enable for debugging!
|
System.out.println(glGetProgramInfoLog(shaderProgram.getId())); // Enable for debugging!
|
||||||
projectionUniform = shaderProgram.getUniform("projectionTransform");
|
projectionUniform = shaderProgram.getUniform("projectionTransform");
|
||||||
viewUniform = shaderProgram.getUniform("viewTransform");
|
viewUniform = shaderProgram.getUniform("viewTransform");
|
||||||
modelUniform = shaderProgram.getUniform("modelTransform");
|
modelUniform = shaderProgram.getUniform("modelTransform");
|
||||||
|
normalUniform = shaderProgram.getUniform("normalTransform");
|
||||||
colorUniform = shaderProgram.getUniform("aspectColor");
|
colorUniform = shaderProgram.getUniform("aspectColor");
|
||||||
textureSamplerUniform = shaderProgram.getUniform("textureSampler");
|
textureSamplerUniform = shaderProgram.getUniform("textureSampler");
|
||||||
}
|
}
|
||||||
|
@ -46,13 +49,15 @@ public class ModelRenderer {
|
||||||
glUniform3f(colorUniform, color.x, color.y, color.z);
|
glUniform3f(colorUniform, color.x, color.y, color.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(Model model, Matrix4f modelTransform) {
|
public void render(Model model, Matrix4f modelTransform, Matrix3f normalTransform) {
|
||||||
glUniformMatrix4fv(modelUniform, false, modelTransform.get(new float[16]));
|
glUniformMatrix4fv(modelUniform, false, modelTransform.get(new float[16]));
|
||||||
|
glUniformMatrix3fv(normalUniform, false, normalTransform.get(new float[16]));
|
||||||
model.draw();
|
model.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(Model model, float[] transformData) {
|
public void render(Model model, float[] transformData, float[] normalData) {
|
||||||
glUniformMatrix4fv(modelUniform, false, transformData);
|
glUniformMatrix4fv(modelUniform, false, transformData);
|
||||||
|
glUniformMatrix3fv(normalUniform, false, normalData);
|
||||||
model.draw();
|
model.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ public class GUIRenderer {
|
||||||
private final int vertexCount;
|
private final int vertexCount;
|
||||||
private final ShaderProgram shaderProgram;
|
private final ShaderProgram shaderProgram;
|
||||||
private final int transformUniformLocation;
|
private final int transformUniformLocation;
|
||||||
|
private final int textureSamplerUniform;
|
||||||
private final Matrix4f transformMatrix;
|
private final Matrix4f transformMatrix;
|
||||||
private final float[] transformMatrixData;
|
private final float[] transformMatrixData;
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ public class GUIRenderer {
|
||||||
.withShader("shader/gui/fragment.glsl", GL_FRAGMENT_SHADER)
|
.withShader("shader/gui/fragment.glsl", GL_FRAGMENT_SHADER)
|
||||||
.build();
|
.build();
|
||||||
transformUniformLocation = shaderProgram.getUniform("transform");
|
transformUniformLocation = shaderProgram.getUniform("transform");
|
||||||
|
textureSamplerUniform = shaderProgram.getUniform("guiTexture");
|
||||||
shaderProgram.bindAttribute(0, "position");
|
shaderProgram.bindAttribute(0, "position");
|
||||||
this.transformMatrix = new Matrix4f();
|
this.transformMatrix = new Matrix4f();
|
||||||
this.transformMatrixData = new float[16];
|
this.transformMatrixData = new float[16];
|
||||||
|
@ -67,6 +69,7 @@ public class GUIRenderer {
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
glDisable(GL_DEPTH_TEST);
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
glUniform1i(textureSamplerUniform, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void draw(String name, float scaleX, float scaleY, float x, float y) {
|
public void draw(String name, float scaleX, float scaleY, float x, float y) {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package nl.andrewl.aos2_client.render.model;
|
||||||
|
|
||||||
|
public interface ModelRenderable {
|
||||||
|
float[] getModelTransformData();
|
||||||
|
float[] getNormalTransformData();
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
#version 460 core
|
#version 460 core
|
||||||
|
|
||||||
in vec2 textureCoords;
|
in vec2 textureCoords;
|
||||||
in vec3 vertexNormal;
|
in vec3 vertexColor;
|
||||||
|
|
||||||
out vec4 fragmentColor;
|
out vec4 fragmentColor;
|
||||||
|
|
||||||
|
@ -14,12 +14,7 @@ void main() {
|
||||||
if (baseColor == templateColor) {
|
if (baseColor == templateColor) {
|
||||||
baseColor = vec4(aspectColor, 1.0);
|
baseColor = vec4(aspectColor, 1.0);
|
||||||
}
|
}
|
||||||
vec3 lightDirection = normalize(vec3(0.5, -1.0, -0.5));// TODO: Add this via a uniform.
|
|
||||||
vec3 lightColor = vec3(1.0, 1.0, 0.9); // TODO: Add this via a uniform.
|
|
||||||
|
|
||||||
vec3 ambientComponent = vec3(0.1, 0.1, 0.1);
|
|
||||||
vec3 diffuseComponent = max(dot(vertexNormal * -1, lightDirection), 0.0) * lightColor;
|
fragmentColor = vec4(vertexColor * vec3(baseColor), 1.0);
|
||||||
// TODO: Add shading based on light.
|
|
||||||
// fragmentColor = vec4((ambientComponent + diffuseComponent), 1.0) * baseColor;
|
|
||||||
fragmentColor = baseColor;
|
|
||||||
}
|
}
|
|
@ -7,12 +7,21 @@ layout (location = 2) in vec2 textureCoordsIn;
|
||||||
uniform mat4 projectionTransform;
|
uniform mat4 projectionTransform;
|
||||||
uniform mat4 viewTransform;
|
uniform mat4 viewTransform;
|
||||||
uniform mat4 modelTransform;
|
uniform mat4 modelTransform;
|
||||||
|
uniform mat3 normalTransform;
|
||||||
|
|
||||||
out vec2 textureCoords;
|
out vec2 textureCoords;
|
||||||
out vec3 vertexNormal;
|
out vec3 vertexColor;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = projectionTransform * viewTransform * modelTransform * vec4(vertexPositionIn, 1.0);
|
gl_Position = projectionTransform * viewTransform * modelTransform * vec4(vertexPositionIn, 1.0);
|
||||||
vertexNormal = vec3(modelTransform * vec4(vertexNormalIn, 1.0));
|
vec3 vertexNormal = normalize(normalTransform * vertexNormalIn);
|
||||||
|
|
||||||
textureCoords = textureCoordsIn;
|
textureCoords = textureCoordsIn;
|
||||||
|
|
||||||
|
vec3 lightDirection = normalize(vec3(0.5, -1.0, -0.5));// TODO: Add this via a uniform.
|
||||||
|
vec3 lightColor = vec3(1.0, 1.0, 0.9); // TODO: Add this via a uniform.
|
||||||
|
|
||||||
|
float ambientComponent = 0.1;
|
||||||
|
float diffuseComponent = max(dot(-vertexNormal, lightDirection), 0.0);
|
||||||
|
vertexColor = (ambientComponent + diffuseComponent) * lightColor;
|
||||||
}
|
}
|
Loading…
Reference in New Issue