un-hardcode fog config
This commit is contained in:
71
src/main/kotlin/CustomDefaultShader.kt
Normal file
71
src/main/kotlin/CustomDefaultShader.kt
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package gay.pizza.CavesOfJolk
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.g3d.Attributes
|
||||||
|
import com.badlogic.gdx.graphics.g3d.Renderable
|
||||||
|
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute
|
||||||
|
import com.badlogic.gdx.graphics.g3d.shaders.DefaultShader
|
||||||
|
|
||||||
|
class CustomDefaultShader(renderable: Renderable, config: Config):
|
||||||
|
DefaultShader(renderable, config, createPrefix(renderable, config))
|
||||||
|
{
|
||||||
|
companion object
|
||||||
|
{
|
||||||
|
fun createPrefix(renderable: Renderable, config: Config): String
|
||||||
|
{
|
||||||
|
var prefix = DefaultShader.createPrefix(renderable, config)
|
||||||
|
|
||||||
|
val attribs = Attributes()
|
||||||
|
renderable.environment.let { attribs.set(it) }
|
||||||
|
renderable.material.let { attribs.set(it) }
|
||||||
|
|
||||||
|
if (attribs.has(ColorAttribute.Fog))
|
||||||
|
{
|
||||||
|
prefix += "#define fog${
|
||||||
|
when (attribs.get<CustomIntAttribute>(CustomIntAttribute.FogMode)?.value
|
||||||
|
?: CustomIntAttribute.FogModes.Distance.toInt()) {
|
||||||
|
CustomIntAttribute.FogModes.Distance.toInt() -> "Distance"
|
||||||
|
CustomIntAttribute.FogModes.Depth.toInt() -> "Depth"
|
||||||
|
else -> throw IndexOutOfBoundsException()
|
||||||
|
}
|
||||||
|
}Flag\n"
|
||||||
|
prefix += "#define fog${
|
||||||
|
when (attribs.get<CustomIntAttribute>(CustomIntAttribute.FogType)?.value
|
||||||
|
?: CustomIntAttribute.FogTypes.Original.toInt()) {
|
||||||
|
CustomIntAttribute.FogTypes.Original.toInt() -> "Original"
|
||||||
|
CustomIntAttribute.FogTypes.Linear.toInt() -> "Linear"
|
||||||
|
CustomIntAttribute.FogTypes.Smooth.toInt() -> "Smooth"
|
||||||
|
CustomIntAttribute.FogTypes.InvSquare.toInt() -> "InvSquare"
|
||||||
|
CustomIntAttribute.FogTypes.Exp.toInt() -> "Exp"
|
||||||
|
CustomIntAttribute.FogTypes.Exp2.toInt() -> "Exp2"
|
||||||
|
else -> throw IndexOutOfBoundsException()
|
||||||
|
}
|
||||||
|
}Flag\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
object Inputs
|
||||||
|
{
|
||||||
|
val fogNear = Uniform("u_fogNear")
|
||||||
|
val fogFar = Uniform("u_fogFar")
|
||||||
|
val fogDensity = Uniform("u_fogDensity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val u_fogNear = register(Inputs.fogNear)
|
||||||
|
private val u_fogFar = register(Inputs.fogFar)
|
||||||
|
private val u_fogDensity = register(Inputs.fogDensity)
|
||||||
|
|
||||||
|
override fun bindLights(renderable: Renderable?, attributes: Attributes?)
|
||||||
|
{
|
||||||
|
if (attributes == null)
|
||||||
|
return
|
||||||
|
|
||||||
|
attributes.get<CustomFloatAttribute>(CustomFloatAttribute.FogNear)?.let { set(u_fogNear, it.value) }
|
||||||
|
attributes.get<CustomFloatAttribute>(CustomFloatAttribute.FogFar)?.let { set(u_fogFar, it.value) }
|
||||||
|
attributes.get<CustomFloatAttribute>(CustomFloatAttribute.FogDensity)?.let { set(u_fogDensity, it.value) }
|
||||||
|
|
||||||
|
super.bindLights(renderable, attributes)
|
||||||
|
}
|
||||||
|
}
|
34
src/main/kotlin/CustomDefaultShaderProvider.kt
Normal file
34
src/main/kotlin/CustomDefaultShaderProvider.kt
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package gay.pizza.CavesOfJolk
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx
|
||||||
|
import com.badlogic.gdx.graphics.g3d.Renderable
|
||||||
|
import com.badlogic.gdx.graphics.g3d.Shader
|
||||||
|
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute
|
||||||
|
import com.badlogic.gdx.graphics.g3d.shaders.DefaultShader
|
||||||
|
import com.badlogic.gdx.graphics.g3d.utils.DefaultShaderProvider
|
||||||
|
|
||||||
|
class CustomDefaultShaderProvider(config: DefaultShader.Config): DefaultShaderProvider(config)
|
||||||
|
{
|
||||||
|
init
|
||||||
|
{
|
||||||
|
if (config.vertexShader == null)
|
||||||
|
config.vertexShader = Gdx.files.internal("lit.vert.glsl").readString()
|
||||||
|
if (config.fragmentShader == null)
|
||||||
|
config.fragmentShader = Gdx.files.internal("lit.frag.glsl").readString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createShader(renderable: Renderable): Shader
|
||||||
|
{
|
||||||
|
val renderableMask = renderable.environment.mask or renderable.material.mask
|
||||||
|
|
||||||
|
val fogMask =
|
||||||
|
CustomIntAttribute.FogMode or
|
||||||
|
CustomIntAttribute.FogType or
|
||||||
|
CustomFloatAttribute.FogNear or
|
||||||
|
CustomFloatAttribute.FogFar or
|
||||||
|
CustomFloatAttribute.FogDensity
|
||||||
|
if ((renderableMask and ColorAttribute.Fog == ColorAttribute.Fog) && (renderableMask and fogMask != 0L))
|
||||||
|
return CustomDefaultShader(renderable, config)
|
||||||
|
return super.createShader(renderable)
|
||||||
|
}
|
||||||
|
}
|
21
src/main/kotlin/CustomFloatAttribute.kt
Normal file
21
src/main/kotlin/CustomFloatAttribute.kt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package gay.pizza.CavesOfJolk
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.g3d.attributes.FloatAttribute
|
||||||
|
|
||||||
|
class CustomFloatAttribute private constructor(type: Long, value: Float): FloatAttribute(type, value)
|
||||||
|
{
|
||||||
|
companion object
|
||||||
|
{
|
||||||
|
const val FogNearAlias = "fogNear"
|
||||||
|
val FogNear = register(FogNearAlias)
|
||||||
|
fun createFogNear(value: Float) = CustomFloatAttribute(FogNear, value)
|
||||||
|
|
||||||
|
const val FogFarAlias = "fogFar"
|
||||||
|
val FogFar = register(FogFarAlias)
|
||||||
|
fun createFogFar(value: Float) = CustomFloatAttribute(FogFar, value)
|
||||||
|
|
||||||
|
const val FogDensityAlias = "fogDensity"
|
||||||
|
val FogDensity = register(FogDensityAlias)
|
||||||
|
fun createFogDensity(value: Float) = CustomFloatAttribute(FogDensity, value)
|
||||||
|
}
|
||||||
|
}
|
37
src/main/kotlin/CustomIntAttribute.kt
Normal file
37
src/main/kotlin/CustomIntAttribute.kt
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package gay.pizza.CavesOfJolk
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.g3d.attributes.IntAttribute
|
||||||
|
|
||||||
|
class CustomIntAttribute private constructor(type: Long, value: Int): IntAttribute(type, value)
|
||||||
|
{
|
||||||
|
enum class FogModes(private val value: Int)
|
||||||
|
{
|
||||||
|
Distance(0),
|
||||||
|
Depth(1);
|
||||||
|
|
||||||
|
fun toInt() = value
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class FogTypes(private val value: Int)
|
||||||
|
{
|
||||||
|
Original(0),
|
||||||
|
Linear(1),
|
||||||
|
Smooth(2),
|
||||||
|
InvSquare(3),
|
||||||
|
Exp(4),
|
||||||
|
Exp2(5);
|
||||||
|
|
||||||
|
fun toInt() = value
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object
|
||||||
|
{
|
||||||
|
const val FogModeAlias = "fogMode"
|
||||||
|
val FogMode = register(FogModeAlias)
|
||||||
|
fun createFogMode(value: FogModes) = CustomIntAttribute(FogMode, value.toInt())
|
||||||
|
|
||||||
|
const val FogTypeAlias = "fogType"
|
||||||
|
val FogType = register(FogTypeAlias)
|
||||||
|
fun createFogType(value: FogTypes) = CustomIntAttribute(FogType, value.toInt())
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@ package gay.pizza.CavesOfJolk
|
|||||||
|
|
||||||
import com.badlogic.gdx.ApplicationAdapter
|
import com.badlogic.gdx.ApplicationAdapter
|
||||||
import com.badlogic.gdx.Gdx
|
import com.badlogic.gdx.Gdx
|
||||||
import com.badlogic.gdx.audio.Sound
|
|
||||||
import com.badlogic.gdx.graphics.*
|
import com.badlogic.gdx.graphics.*
|
||||||
import com.badlogic.gdx.graphics.g2d.BitmapFont
|
import com.badlogic.gdx.graphics.g2d.BitmapFont
|
||||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||||
@ -13,7 +12,6 @@ import com.badlogic.gdx.graphics.g3d.attributes.IntAttribute
|
|||||||
import com.badlogic.gdx.graphics.g3d.attributes.TextureAttribute
|
import com.badlogic.gdx.graphics.g3d.attributes.TextureAttribute
|
||||||
import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight
|
import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight
|
||||||
import com.badlogic.gdx.graphics.g3d.shaders.DefaultShader
|
import com.badlogic.gdx.graphics.g3d.shaders.DefaultShader
|
||||||
import com.badlogic.gdx.graphics.g3d.utils.DefaultShaderProvider
|
|
||||||
import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder
|
import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder
|
||||||
import com.badlogic.gdx.graphics.g3d.utils.TextureDescriptor
|
import com.badlogic.gdx.graphics.g3d.utils.TextureDescriptor
|
||||||
import com.badlogic.gdx.math.*
|
import com.badlogic.gdx.math.*
|
||||||
@ -24,7 +22,6 @@ class Game: ApplicationAdapter()
|
|||||||
{
|
{
|
||||||
private lateinit var texJolk: Texture
|
private lateinit var texJolk: Texture
|
||||||
private lateinit var fntComic: BitmapFont
|
private lateinit var fntComic: BitmapFont
|
||||||
private lateinit var nut: Sound
|
|
||||||
private lateinit var cube: Model
|
private lateinit var cube: Model
|
||||||
private lateinit var floor: Model
|
private lateinit var floor: Model
|
||||||
|
|
||||||
@ -95,18 +92,28 @@ class Game: ApplicationAdapter()
|
|||||||
spriteBatch = SpriteBatch()
|
spriteBatch = SpriteBatch()
|
||||||
|
|
||||||
env = Environment()
|
env = Environment()
|
||||||
env.set(IntAttribute.createCullFace(GL20.GL_BACK))
|
env.set(
|
||||||
env.set(ColorAttribute.createAmbientLight(XnaColor.DarkSlateGray.lighten(-0.6)))
|
IntAttribute.createCullFace(GL20.GL_BACK),
|
||||||
env.set(ColorAttribute.createFog(XnaColor.CornflowerBlue))
|
ColorAttribute.createAmbientLight(XnaColor.DarkSlateGray.lighten(-0.6)),
|
||||||
|
ColorAttribute.createFog(XnaColor.CornflowerBlue))
|
||||||
|
env.set(
|
||||||
|
CustomIntAttribute.createFogMode(CustomIntAttribute.FogModes.Depth),
|
||||||
|
CustomIntAttribute.createFogType(CustomIntAttribute.FogTypes.Smooth),
|
||||||
|
CustomFloatAttribute.createFogNear(0.75f),
|
||||||
|
CustomFloatAttribute.createFogFar(20.5f))
|
||||||
|
/*
|
||||||
|
env.set(
|
||||||
|
CustomIntAttribute.createFogMode(CustomIntAttribute.FogModes.Distance),
|
||||||
|
CustomIntAttribute.createFogType(CustomIntAttribute.FogTypes.Exp2),
|
||||||
|
CustomFloatAttribute.createFogDensity(0.1f))
|
||||||
|
*/
|
||||||
env.add(DirectionalLight().set(XnaColor.White, Vector3(1.0f, -1.0f, -1.0f).nor()))
|
env.add(DirectionalLight().set(XnaColor.White, Vector3(1.0f, -1.0f, -1.0f).nor()))
|
||||||
|
|
||||||
val shaderConfig = DefaultShader.Config(
|
val shaderConfig = DefaultShader.Config()
|
||||||
Gdx.files.internal("lit.vert.glsl").readString(),
|
|
||||||
Gdx.files.internal("lit.frag.glsl").readString())
|
|
||||||
shaderConfig.numDirectionalLights = 1
|
shaderConfig.numDirectionalLights = 1
|
||||||
shaderConfig.numPointLights = 0
|
shaderConfig.numPointLights = 0
|
||||||
shaderConfig.numBones = 0
|
shaderConfig.numBones = 0
|
||||||
modelBatch = ModelBatch(DefaultShaderProvider(shaderConfig))
|
modelBatch = ModelBatch(CustomDefaultShaderProvider(shaderConfig))
|
||||||
|
|
||||||
colin = Colin()
|
colin = Colin()
|
||||||
|
|
||||||
@ -136,9 +143,7 @@ class Game: ApplicationAdapter()
|
|||||||
val deltaTime = Gdx.graphics.deltaTime
|
val deltaTime = Gdx.graphics.deltaTime
|
||||||
update(deltaTime)
|
update(deltaTime)
|
||||||
|
|
||||||
val fogColour = XnaColor.CornflowerBlue.lighten(Math.sin(0.056 * jolkRot.toDouble()) * 0.15 - 0.15)
|
ScreenUtils.clear(XnaColor.CornflowerBlue, true)
|
||||||
ScreenUtils.clear(fogColour, true)
|
|
||||||
env.set(ColorAttribute.createFog(fogColour))
|
|
||||||
|
|
||||||
val jolkPos = Vector3(0.0f, 1.0f + MathUtils.sin(jolkRot * 0.25f) * 0.25f, -4.0f)
|
val jolkPos = Vector3(0.0f, 1.0f + MathUtils.sin(jolkRot * 0.25f) * 0.25f, -4.0f)
|
||||||
val world = Matrix4()
|
val world = Matrix4()
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package gay.pizza.CavesOfJolk
|
package gay.pizza.CavesOfJolk
|
||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.graphics.g3d.Attribute
|
||||||
|
import com.badlogic.gdx.graphics.g3d.Attributes
|
||||||
import com.badlogic.gdx.math.MathUtils
|
import com.badlogic.gdx.math.MathUtils
|
||||||
import com.badlogic.gdx.math.Vector2
|
import com.badlogic.gdx.math.Vector2
|
||||||
import ktx.math.div
|
import ktx.math.div
|
||||||
@ -58,3 +60,7 @@ fun Color.mix(rhs: Color, x: Float) = Color(
|
|||||||
MathUtils.lerp(this.g, rhs.g, x),
|
MathUtils.lerp(this.g, rhs.g, x),
|
||||||
MathUtils.lerp(this.b, rhs.b, x),
|
MathUtils.lerp(this.b, rhs.b, x),
|
||||||
MathUtils.lerp(this.a, rhs.a, x))
|
MathUtils.lerp(this.a, rhs.a, x))
|
||||||
|
|
||||||
|
//FIXME: find some way to get rid of these warnings
|
||||||
|
@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "UNCHECKED_CAST")
|
||||||
|
fun <T: Attribute> Attributes.get(type: Long) = get(type) as? T
|
||||||
|
@ -88,7 +88,14 @@ uniform vec4 u_cameraPosition;
|
|||||||
|
|
||||||
#ifdef fogFlag
|
#ifdef fogFlag
|
||||||
uniform vec4 u_fogColor;
|
uniform vec4 u_fogColor;
|
||||||
varying float v_fog;
|
|
||||||
|
#if defined(fogLinearFlag) || defined(fogSmoothFlag) || defined(fogInvSquareFlag)
|
||||||
|
uniform float u_fogNear;
|
||||||
|
uniform float u_fogFar;
|
||||||
|
#elif defined(fogExpFlag) || defined(fogExp2Flag)
|
||||||
|
uniform float u_fogDensity;
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // fogFlag
|
#endif // fogFlag
|
||||||
|
|
||||||
uniform mat4 u_projViewTrans;
|
uniform mat4 u_projViewTrans;
|
||||||
@ -174,68 +181,42 @@ void main()
|
|||||||
fragment = diffuse.rgb;
|
fragment = diffuse.rgb;
|
||||||
#endif // lightingFlag
|
#endif // lightingFlag
|
||||||
|
|
||||||
#ifdef fogFlag
|
#ifdef fogOriginalFlag
|
||||||
#define fogDistance
|
vec3 fogVec = u_cameraPosition.xyz - v_worldPosition.xyz;
|
||||||
//#define fogDepth
|
float fogDist = dot(fogVec, fogVec);
|
||||||
|
float fog = min(fogDist * u_cameraPosition.w, 1.0);
|
||||||
|
#elif defined(fogDistanceFlag)
|
||||||
|
float fogDist = length(v_worldPosition.xyz - u_cameraPosition.xyz);
|
||||||
|
#elif defined(fogDepthFlag)
|
||||||
|
float fogDist = gl_FragCoord.z / gl_FragCoord.w;
|
||||||
|
#endif // fogOriginalFlag || fogDistanceFlag || fogDepthFlag
|
||||||
|
|
||||||
//#define fogOriginal
|
#ifdef fogLinearFlag
|
||||||
//#define fogLinear
|
|
||||||
#define fogSmooth
|
|
||||||
//#define fogInvSquare
|
|
||||||
//#define fogExp
|
|
||||||
//#define fogExp2
|
|
||||||
|
|
||||||
#if defined(fogLinear) || defined(fogSmooth) || defined(fogInvSquare)
|
|
||||||
float near = 1.5;
|
|
||||||
float far = 20.5;
|
|
||||||
#elif defined(fogExp) || defined(fogExp2)
|
|
||||||
float density = 0.12;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef fogOriginal
|
|
||||||
vec3 fvec = u_cameraPosition.xyz - v_worldPosition.xyz;
|
|
||||||
float flen = dot(fvec, fvec);
|
|
||||||
float fog = min(flen * u_cameraPosition.w, 1.0);
|
|
||||||
#elif defined(fogDistance)
|
|
||||||
float flen = length(v_worldPosition.xyz - u_cameraPosition.xyz);
|
|
||||||
#elif defined(fogDepth)
|
|
||||||
float flen = gl_FragCoord.z / gl_FragCoord.w;
|
|
||||||
#endif // fogOriginal || fogDistance || fogDepth
|
|
||||||
|
|
||||||
#ifdef fogLinear
|
|
||||||
// fog = saturate(linearstep(near, far, x))
|
// fog = saturate(linearstep(near, far, x))
|
||||||
float fog = clamp((flen - near) / (far - near), 0.0, 1.0);
|
float fog = clamp((fogDist - u_fogNear) / (u_fogFar - u_fogNear), 0.0, 1.0);
|
||||||
#elif defined(fogSmooth)
|
#elif defined(fogSmoothFlag)
|
||||||
// fog = smoothstep(saturate(linearstep(near, far, x)))
|
// fog = smoothstep(saturate(linearstep(near, far, x)))
|
||||||
float fog = clamp((flen - near) / (far - near), 0.0, 1.0);
|
float fog = clamp((fogDist - u_fogNear) / (u_fogFar - u_fogNear), 0.0, 1.0);
|
||||||
fog = fog * fog * (3.0 - 2.0 * fog);
|
fog = fog * fog * (3.0 - 2.0 * fog);
|
||||||
//fog = fog * fog * fog * (fog * (6.0 * fog - 15.0) + 10.0);
|
//fog = fog * fog * fog * (fog * (6.0 * fog - 15.0) + 10.0);
|
||||||
#elif defined(fogInvSquare)
|
#elif defined(fogInvSquareFlag)
|
||||||
// fog = isqstep(saturate(linearstep(near, far, x)))
|
// fog = isqstep(saturate(linearstep(near, far, x)))
|
||||||
float fog = clamp((flen - near) / (far - near), 0.0, 1.0);
|
float fog = clamp((fogDist - u_fogNear) / (u_fogFar - u_fogNear), 0.0, 1.0);
|
||||||
fog = 1.0 - fog;
|
fog = 1.0 - fog;
|
||||||
fog = 1.0 - fog * fog;
|
fog = 1.0 - fog * fog;
|
||||||
#elif defined(fogExp)
|
#elif defined(fogExpFlag)
|
||||||
float fog = max(1.0 - exp(-density * flen), 0.0);
|
// todo: can precompute some stuff in uniforms
|
||||||
#elif defined(fogExp2)
|
float fog = max(1.0 - exp(-u_fogDensity * fogDist), 0.0);
|
||||||
float dz = density * flen;
|
#elif defined(fogExp2Flag)
|
||||||
|
// todo: can precompute some stuff in uniforms
|
||||||
|
float dz = u_fogDensity * fogDist;
|
||||||
float fog = max(1.0 - exp(-dz * dz), 0.0);
|
float fog = max(1.0 - exp(-dz * dz), 0.0);
|
||||||
#endif // fogLinear || fogSmooth || fogInvSquare || fogExp || fogExp2
|
#endif // fogLinearFlag || fogSmoothFlag || fogInvSquareFlag || fogExpFlag || fogExp2Flag
|
||||||
|
|
||||||
|
#ifdef fogFlag
|
||||||
vec3 fogColor = u_fogColor.rgb;
|
vec3 fogColor = u_fogColor.rgb;
|
||||||
fogColor = linearEncode(fogColor);
|
fogColor = linearEncode(fogColor);
|
||||||
fragment = mix(fragment, fogColor, fog);
|
fragment = mix(fragment, fogColor, fog);
|
||||||
#endif // fogFlag
|
#endif // fogFlag
|
||||||
gl_FragColor = vec4(linearDecode(fragment), diffuse.a);
|
gl_FragColor = vec4(linearDecode(fragment), diffuse.a);
|
||||||
/*
|
|
||||||
if (gl_FragCoord.x > 1280.0)
|
|
||||||
{
|
|
||||||
if (fog <= 0.0)
|
|
||||||
gl_FragColor = vec4(1.0, 0.0, 1.0, diffuse.a);
|
|
||||||
else if (fog >= 1.0)
|
|
||||||
gl_FragColor = vec4(1.0, 0.0, 0.0, diffuse.a);
|
|
||||||
else
|
|
||||||
gl_FragColor = vec4(fog, fog, 0.0, diffuse.a);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user