From 38387546116f45fb2debb3fe017b220845c0c01b Mon Sep 17 00:00:00 2001 From: josua Date: Sat, 27 Jun 2020 22:01:57 +1000 Subject: [PATCH] Added dynamic break particles, improved the particle system --- .../display/DisplayLighting.java | 6 +- src/projectzombie/display/DisplayRender.java | 3 +- src/projectzombie/display/DisplayWindow.java | 88 ++++ src/projectzombie/entity/Entity.java | 4 +- src/projectzombie/entity/EntityBullet.java | 2 +- src/projectzombie/entity/EntityExplosion.java | 1 - .../entity/particle/ParticleBlood.java | 78 +--- .../entity/particle/ParticleBreak.java | 383 ++++++++++++------ .../entity/particle/ParticleWater.java | 61 +-- .../entity/player/EntityPlayer.java | 28 +- src/projectzombie/init/Models.java | 1 - src/projectzombie/menu/MenuDeath.java | 1 - src/projectzombie/menu/gui/Label.java | 5 - src/projectzombie/model/IModel.java | 21 + src/projectzombie/model/Model.java | 62 +-- src/projectzombie/model/ModelChunk.java | 5 + src/projectzombie/model/ModelEmpty.java | 7 +- src/projectzombie/model/ModelGrass.java | 5 + src/projectzombie/model/ModelGui.java | 8 +- src/projectzombie/model/ModelTile.java | 5 + src/projectzombie/model/ModelTree.java | 5 + src/projectzombie/model/ModelVertical.java | 11 +- src/projectzombie/tiles/TileBossPortal.java | 3 +- src/projectzombie/tiles/TileWater.java | 2 +- src/projectzombie/util/math/TileState.java | 1 - src/projectzombie/world/chunk/Chunk.java | 9 +- src/projectzombie/world/chunk/ChunkEmpty.java | 1 - .../world/layer/layergen/LayerGenEarth.java | 6 +- src/resources/shader/effectRenderer.fsh | 48 +++ src/resources/shader/effectRenderer.vsh | 11 + src/resources/shader/environmentRenderer.fsh | 8 + src/resources/shader/environmentRenderer.vsh | 6 +- src/resources/texture/particle/blood.png | Bin 5597 -> 1136 bytes 33 files changed, 554 insertions(+), 331 deletions(-) create mode 100644 src/projectzombie/model/IModel.java create mode 100644 src/resources/shader/effectRenderer.fsh create mode 100644 src/resources/shader/effectRenderer.vsh diff --git a/src/projectzombie/display/DisplayLighting.java b/src/projectzombie/display/DisplayLighting.java index 2ade1ec..8e1ad77 100755 --- a/src/projectzombie/display/DisplayLighting.java +++ b/src/projectzombie/display/DisplayLighting.java @@ -1,7 +1,6 @@ package projectzombie.display; import java.nio.ByteBuffer; -import java.util.Arrays; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL33; @@ -9,7 +8,6 @@ import org.lwjgl.opengl.GL33; import bdf.types.BdfNamedList; import bdf.types.BdfObject; import gl_engine.MathHelpers; -import gl_engine.range.Range2i; import gl_engine.vec.Vec2d; import gl_engine.vec.Vec2i; import projectzombie.Main; @@ -267,8 +265,8 @@ public class DisplayLighting Vec2i tpos = new Vec2i(x + lighting.x * 16, y + lighting.y * 16); // Store light level data from the image - layer.setDaylightLevel(lighting.p[i+0], tpos); - layer.setLightLevel(lighting.p[i+1], tpos); + layer.setDaylightLevel(lighting.p[i*4+0], tpos); + layer.setLightLevel(lighting.p[i*4+1], tpos); } lighting_new = false; diff --git a/src/projectzombie/display/DisplayRender.java b/src/projectzombie/display/DisplayRender.java index 8c9d649..dc59af8 100755 --- a/src/projectzombie/display/DisplayRender.java +++ b/src/projectzombie/display/DisplayRender.java @@ -10,7 +10,6 @@ import org.lwjgl.opengl.GL33; import gl_engine.matrix.Matrix4; import projectzombie.Main; import projectzombie.entity.player.EntityPlayer; -import projectzombie.init.Layers; import projectzombie.model.Model; import projectzombie.util.math.ColorRange; import projectzombie.world.chunk.ChunkEventHandler; @@ -45,11 +44,13 @@ public class DisplayRender projection = Matrix4.multiply(camera.getMatrix(), projection); Matrix4 rotated = Matrix4.rotate(-camera.angle, 0, 1, 0); + Matrix4 billboard = Matrix4.multiply(Matrix4.rotate(-45, 1, 0, 0), rotated); // Process all the light sources //DynamicLighting.update(); Main.window.environmentRenderer.use(); + GL33.glUniformMatrix4fv(Main.window.glsl_billboard, true, billboard.getArray()); GL33.glUniformMatrix4fv(Main.window.glsl_projection, true, projection.getArray()); GL33.glUniformMatrix4fv(Main.window.glsl_rotated, true, rotated.getArray()); GL33.glUniform1i(Main.window.glsl_time, (int)((System.currentTimeMillis() / 10) % 1000)); diff --git a/src/projectzombie/display/DisplayWindow.java b/src/projectzombie/display/DisplayWindow.java index be21538..d217ae4 100755 --- a/src/projectzombie/display/DisplayWindow.java +++ b/src/projectzombie/display/DisplayWindow.java @@ -1,5 +1,9 @@ package projectzombie.display; +import static org.lwjgl.opengl.GL11.GL_FLOAT; +import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray; +import static org.lwjgl.opengl.GL20.glVertexAttribPointer; + import java.nio.IntBuffer; import org.lwjgl.BufferUtils; @@ -9,6 +13,7 @@ import org.lwjgl.opengl.GL33; import gl_engine.graphics.GraphicsHelpers; import gl_engine.graphics.GraphicsShader; import mainloop.task.IMainloopTask; +import projectzombie.Main; import projectzombie.init.Resources; import projectzombie.input.CursorEnterCallback; import projectzombie.input.CursorPosCallback; @@ -31,12 +36,17 @@ public class DisplayWindow implements IMainloopTask public static int fps = 0; public GraphicsShader environmentRenderer; + public GraphicsShader effectRenderer; + + public int effect_vao; public int glsl_color; + public int glsl_contrast; public int glsl_tex_cut; public int glsl_model; public int glsl_projection; public int glsl_rotated; + public int glsl_billboard; public int glsl_time; public int glsl_day_low; @@ -45,6 +55,13 @@ public class DisplayWindow implements IMainloopTask public int glsl_lightmap_offset; public int glsl_lightmap_size; + public int glsl_effect_time; + public int glsl_effect_red; + public int glsl_effect_vortex; + public int glsl_effect_vsnow; + public int glsl_effect_red_freq; + public int glsl_effect_chill; + public int getWidth() { return this.width; } @@ -110,6 +127,8 @@ public class DisplayWindow implements IMainloopTask glsl_time = GL33.glGetUniformLocation(environmentRenderer.program, "time"); glsl_tex_cut = GL33.glGetUniformLocation(environmentRenderer.program, "tex_cut"); glsl_color = GL33.glGetUniformLocation(environmentRenderer.program, "color"); + glsl_contrast = GL33.glGetUniformLocation(environmentRenderer.program, "contrast"); + glsl_billboard = GL33.glGetUniformLocation(environmentRenderer.program, "billboard"); glsl_day_low = GL33.glGetUniformLocation(environmentRenderer.program, "lighting_day_low"); glsl_day_high = GL33.glGetUniformLocation(environmentRenderer.program, "lighting_day_high"); @@ -122,6 +141,34 @@ public class DisplayWindow implements IMainloopTask GL33.glUniform1i(glsl_atlas, 0); GL33.glUniform1i(glsl_lightmap, 1); + + effectRenderer = new GraphicsShader("/resources/shader/effectRenderer"); + effectRenderer.use(); + + glsl_effect_time = GL33.glGetUniformLocation(effectRenderer.program, "time"); + glsl_effect_red = GL33.glGetUniformLocation(effectRenderer.program, "red"); + glsl_effect_vsnow = GL33.glGetUniformLocation(effectRenderer.program, "vsnow"); + glsl_effect_vortex = GL33.glGetUniformLocation(effectRenderer.program, "vortex"); + glsl_effect_red_freq = GL33.glGetUniformLocation(effectRenderer.program, "red_freq"); + glsl_effect_chill = GL33.glGetUniformLocation(effectRenderer.program, "chill"); + + effect_vao = GL33.glGenVertexArrays(); + GL33.glBindVertexArray(effect_vao); + + int effect_vbo = GL33.glGenBuffers(); + GL33.glBindBuffer(GL33.GL_ARRAY_BUFFER, effect_vbo); + GL33.glBufferData(GL33.GL_ARRAY_BUFFER, new float[] { + -1, -1, + -1, 1, + 1, 1, + + -1, -1, + 1, -1, + 1, 1, + }, GL33.GL_STATIC_DRAW); + + glVertexAttribPointer(0, 2, GL_FLOAT, false, Float.BYTES * 2, 0); + glEnableVertexAttribArray(0); } public void render() @@ -136,6 +183,12 @@ public class DisplayWindow implements IMainloopTask environmentRenderer.use(); DisplayLighting.updateLighting(); + if(Main.player.getHydration() < 0.2) { + GL33.glUniform1f(glsl_contrast, (float)(0.2 - Main.player.getHydration()) * 1.6f); + } else { + GL33.glUniform1f(glsl_contrast, 0); + } + // Bind the texture atlas GL33.glActiveTexture(GL33.GL_TEXTURE0); Resources.ATLAS.bind(); @@ -147,6 +200,41 @@ public class DisplayWindow implements IMainloopTask // Render everything DisplayRender.render(w[0], h[0]); + // Use the effect shader + effectRenderer.use(); + + // Send extra data to the shader + if(!Main.player.dead && Main.menu.doGameloop) { + GL33.glUniform1i(glsl_effect_time, (int)System.currentTimeMillis()); + } + + if(Main.player.getHydration() < 0.2) { + GL33.glUniform1f(glsl_effect_vsnow, (float)(0.2 - Main.player.getHydration())); + GL33.glUniform1f(glsl_effect_vortex, (float)(0.2 - Main.player.getHydration()) / 10); + } else { + GL33.glUniform1f(glsl_effect_vsnow, 0); + GL33.glUniform1f(glsl_effect_vortex, 0); + } + + double player_health = Main.player.getHealth() / Main.player.maxHealth(); + + if(player_health < 0.5) { + GL33.glUniform1f(glsl_effect_red, (float)(0.5 - player_health)); + GL33.glUniform1i(glsl_effect_red_freq, 20); + } else { + GL33.glUniform1f(glsl_effect_red, 0); + } + + if(Main.player.getTemperature() < 0.3) { + GL33.glUniform1f(glsl_effect_chill, (float)(0.3 - Main.player.getTemperature()) / 3 * 10); + } else { + GL33.glUniform1f(glsl_effect_chill, 0); + } + + // Draw a quad to the whole screen for the effects + GL33.glBindVertexArray(effect_vao); + GL33.glDrawArrays(GL33.GL_TRIANGLES, 0, 6); + // Swap the framebuffers and poll events GLFW.glfwSwapBuffers(window); GLFW.glfwPollEvents(); diff --git a/src/projectzombie/entity/Entity.java b/src/projectzombie/entity/Entity.java index d4ad407..52b3e05 100755 --- a/src/projectzombie/entity/Entity.java +++ b/src/projectzombie/entity/Entity.java @@ -14,7 +14,7 @@ import gl_engine.vec.Vec2i; import mainloop.task.IMainloopTask; import projectzombie.Main; import projectzombie.init.Entities; -import projectzombie.model.Model; +import projectzombie.model.IModel; import projectzombie.tiles.Tile; import projectzombie.util.math.TileState; import projectzombie.world.chunk.Chunk; @@ -36,7 +36,7 @@ public abstract class Entity implements IBdfClassManager public int stepOnTileCooldown = 0; private boolean dead = false; - public abstract Model getModel(); + public abstract IModel getModel(); public boolean isDead() { return dead; diff --git a/src/projectzombie/entity/EntityBullet.java b/src/projectzombie/entity/EntityBullet.java index 2710b05..b333c94 100755 --- a/src/projectzombie/entity/EntityBullet.java +++ b/src/projectzombie/entity/EntityBullet.java @@ -155,7 +155,7 @@ public class EntityBullet extends EntityParticle // Spawn some blood particles if(!EntityParticle.DISABLED) { - chunk.spawnEntity(ParticleBlood.createBloodParticles(pos, ea.bloodParticles())); + chunk.spawnEntity(new ParticleBlood(pos)); } // Play the hit noise diff --git a/src/projectzombie/entity/EntityExplosion.java b/src/projectzombie/entity/EntityExplosion.java index 25c4976..7b9eeb8 100755 --- a/src/projectzombie/entity/EntityExplosion.java +++ b/src/projectzombie/entity/EntityExplosion.java @@ -8,7 +8,6 @@ import gl_engine.vec.Vec2i; import gl_engine.vec.Vec3d; import projectzombie.Main; import projectzombie.entity.particle.ParticleBlood; -import projectzombie.entity.particle.ParticleBreak; import projectzombie.entity.particle.ParticleSmoke; import projectzombie.init.Models; import projectzombie.init.Sounds; diff --git a/src/projectzombie/entity/particle/ParticleBlood.java b/src/projectzombie/entity/particle/ParticleBlood.java index 5840781..e6e5c14 100755 --- a/src/projectzombie/entity/particle/ParticleBlood.java +++ b/src/projectzombie/entity/particle/ParticleBlood.java @@ -1,84 +1,16 @@ package projectzombie.entity.particle; -import gl_engine.MathHelpers; import gl_engine.vec.Vec2d; -import gl_engine.vec.Vec3d; -import projectzombie.entity.Entity; -import projectzombie.entity.EntityContainer; -import projectzombie.entity.EntityHeight; -import projectzombie.entity.EntityParticle; import projectzombie.init.Models; -import projectzombie.model.Model; -import projectzombie.util.math.random.RandomHelpers; -import projectzombie.world.chunk.Chunk; -import projectzombie.world.layer.Layer; -public class ParticleBlood extends EntityParticle implements EntityHeight +public class ParticleBlood extends ParticleBreak { - private boolean done = false; - private double time = 1000; - private double height = 0; - private Vec3d velocity; - - @Override - public double getHeight() { - return height; + public ParticleBlood(Vec2d pos, int size) { + super(pos, Models.PARTICLE_BLOOD, size, 0.1); } - - @Override - public void setHeight(double height) { - this.height = height; - } - + public ParticleBlood(Vec2d pos) { - super(pos); - - time = RandomHelpers.randrange(rand, 800, 1200); - velocity = new Vec3d(MathHelpers.moveTowards2(0.01, rand.nextDouble() * 360), rand.nextDouble() * 0.12); + this(pos, 200); } - public static EntityContainer createBloodParticles(Vec2d pos, int size) - { - Entity[] entities = new Entity[size]; - - for(int i=0;i 32 || time < 0) { - this.kill(); + dead = 0; + + for(int i=0;i 0.7) { + health -= temperature - 0.7; + } + + if(hydration <= 0) { + hydration = 0; + health -= 0.1; + } // Rotate left if(MOVE_LEFT) { @@ -231,7 +247,7 @@ public class EntityPlayer extends Entity implements EntityAlive, EntityInventory return; } - ParticleBreak.spawnParticles(chunk, pos, this); + chunk.spawnEntity(new ParticleBreak(pos, getModel())); dead = true; } diff --git a/src/projectzombie/init/Models.java b/src/projectzombie/init/Models.java index 489807a..373689e 100755 --- a/src/projectzombie/init/Models.java +++ b/src/projectzombie/init/Models.java @@ -1,7 +1,6 @@ package projectzombie.init; import gl_engine.vec.Vec2d; -import gl_engine.vec.Vec2i; import projectzombie.model.Model; import projectzombie.model.ModelEmpty; import projectzombie.model.ModelGrass; diff --git a/src/projectzombie/menu/MenuDeath.java b/src/projectzombie/menu/MenuDeath.java index 456de90..ce3d852 100755 --- a/src/projectzombie/menu/MenuDeath.java +++ b/src/projectzombie/menu/MenuDeath.java @@ -6,7 +6,6 @@ import projectzombie.menu.gui.ButtonGroup; import projectzombie.menu.gui.GUI; import projectzombie.menu.gui.components.ButtonBasic; import projectzombie.menu.gui.components.ButtonGroupPause; -import projectzombie.menu.gui.components.LabelPause; import projectzombie.menu.gui.components.OverlayBackground; public class MenuDeath extends Menu diff --git a/src/projectzombie/menu/gui/Label.java b/src/projectzombie/menu/gui/Label.java index f4cac29..5b93777 100755 --- a/src/projectzombie/menu/gui/Label.java +++ b/src/projectzombie/menu/gui/Label.java @@ -9,7 +9,6 @@ public class Label implements GUIComponent { private Vec2d pos = new Vec2d(0, 0); private Vec2d size = new Vec2d(0.5, 0.5); - private Vec3d color = new Vec3d(0.92, 0.92, 0.92); private String text = ""; private Alignment alignment = Alignment.CENTRE; @@ -25,10 +24,6 @@ public class Label implements GUIComponent this.size = size; } - public void setColor(Vec3d color) { - this.color = color; - } - public void setAlign(Alignment alignment) { this.alignment = alignment; } diff --git a/src/projectzombie/model/IModel.java b/src/projectzombie/model/IModel.java new file mode 100644 index 0000000..98dd538 --- /dev/null +++ b/src/projectzombie/model/IModel.java @@ -0,0 +1,21 @@ +package projectzombie.model; + +import gl_engine.matrix.Matrix4; +import gl_engine.texture.TextureRef3D; + +public interface IModel +{ + public abstract int[] getIndicies(); + public abstract float[] getVerticies(); + public abstract TextureRef3D[] getTextures(); + public abstract double getWidth(); + public abstract double getHeight(); + public abstract int getIndexSize(); + public abstract int getSize(); + + public void bind(); + public void render(); + public void free(); + public void setModel(Matrix4 model); + public boolean isLoaded(); +} diff --git a/src/projectzombie/model/Model.java b/src/projectzombie/model/Model.java index d198a87..7231351 100644 --- a/src/projectzombie/model/Model.java +++ b/src/projectzombie/model/Model.java @@ -18,21 +18,37 @@ import gl_engine.matrix.Matrix4; import gl_engine.texture.TextureRef3D; import projectzombie.Main; -public abstract class Model +public abstract class Model implements IModel { + public static final float OFFSET = 0.001f; + public static final int SIZE = 14; + public static IModel bound = null; + int vao, vbo, ibo; boolean loaded = false; - public static final int SIZE = 14; - // px, py, pz, tx, ty - public abstract int[] getIndicies(); - public abstract float[] getVerticies(); - public abstract TextureRef3D[] getTextures(); - public abstract double getHeight(); - public abstract int getIndexSize(); - public abstract int getSize(); - - private static Model bound = null; + public static void setGLArrayAttributes() + { + // aPos + glVertexAttribPointer(0, 3, GL_FLOAT, false, Float.BYTES * SIZE, 0); + glEnableVertexAttribArray(0); + + // aTex + glVertexAttribPointer(1, 3, GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 3); + glEnableVertexAttribArray(1); + + // aTexY + glVertexAttribPointer(2, 2, GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 6); + glEnableVertexAttribArray(2); + + // aChunkOffset + glVertexAttribPointer(3, 3, GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 8); + glEnableVertexAttribArray(3); + + // aFlags + glVertexAttribPointer(4, 3, GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 11); + glEnableVertexAttribArray(4); + } protected void generate() { @@ -51,7 +67,7 @@ public abstract class Model } int size = verticies.length/SIZE; - double k = 0.001; + double k = OFFSET; for(int i=0;i> 17; + value ^= value << 5; + return value; +} + +float nextFloat(int seed) { + seed = xorshift(seed); + return mod(abs(fract(float(seed) / 3141.592653)), 1); +} + +float smoothStep(float a) { + return a * a * (3 - 2 * a); +} + +float squared(float a) { + return a * a; +} + +void main() +{ + float distance = sqrt(pPos.x * pPos.x + pPos.y * pPos.y); + + //float vsnow_v = nextFloat(xorshift(floatBitsToInt(pPos.x)) + xorshift(floatBitsToInt(pPos.y)) + xorshift(time)) * vsnow; + float red_v = distance * ((abs(time / red_freq) % 100) / 100.0) * red; + float vortex_v = (1 - distance) * squared(sin(distance * 10 + (time % 3141) / 100.0)) * vortex; + float chill_v = (distance - 1 + chill) * nextFloat(xorshift(floatBitsToInt(pPos.x)) + xorshift(floatBitsToInt(pPos.y))); + + chill_v = (chill_v < 0.25 ? 0 : 1) * chill; + + FragColor = vec4(red_v, 0, 0, red_v) + vec4(0, 0, 0, vortex_v) + vec4(chill_v, chill_v, chill_v, chill_v); +} \ No newline at end of file diff --git a/src/resources/shader/effectRenderer.vsh b/src/resources/shader/effectRenderer.vsh new file mode 100644 index 0000000..fbd9ed5 --- /dev/null +++ b/src/resources/shader/effectRenderer.vsh @@ -0,0 +1,11 @@ +#version 330 + +layout (location = 0) in vec2 aPos; + +out vec2 pPos; + +void main() +{ + gl_Position = new vec4(aPos, 0, 1); + pPos = aPos; +} \ No newline at end of file diff --git a/src/resources/shader/environmentRenderer.fsh b/src/resources/shader/environmentRenderer.fsh index 6e03785..9bf5c5b 100644 --- a/src/resources/shader/environmentRenderer.fsh +++ b/src/resources/shader/environmentRenderer.fsh @@ -20,6 +20,8 @@ uniform vec2 lightmap_size; uniform vec2 tex_cut; uniform vec4 color; +uniform float contrast; + vec3 color_grass_hot_wet = vec3(0.05, 0.8, 0); vec3 color_grass_hot_dry = vec3(1, 0.6, 0); vec3 color_grass_cold_wet = vec3(0.075, 0.533, 0.047); @@ -71,6 +73,12 @@ void main() FragColor = texture(atlas, pTexture) * (mod(int(pFlags / 4), 2) == 1 ? color_grass : vec4(1,1,1,1)) * color * vec4(biggest(light_day, light_src), 1); + + float saturation = contrast * 2 + 1; + + FragColor.x = min(1, FragColor.x * saturation + contrast); + FragColor.y = min(1, FragColor.y * saturation + contrast); + FragColor.z = min(1, FragColor.z * saturation + contrast); discard(FragColor.a == 0 || (pPos.x > tex_cut.y && tex_cut.x > 0.5)); } \ No newline at end of file diff --git a/src/resources/shader/environmentRenderer.vsh b/src/resources/shader/environmentRenderer.vsh index 90d638e..c913dff 100644 --- a/src/resources/shader/environmentRenderer.vsh +++ b/src/resources/shader/environmentRenderer.vsh @@ -12,6 +12,7 @@ out vec3 pPos; flat out int pFlags; +uniform mat4 billboard; uniform mat4 projection; uniform mat4 model; uniform mat4 rotated; @@ -51,11 +52,8 @@ float getTexY() void main() { int type = int(aFlags.z); - - mat4 do_rotation = rotated; - mat4 no_rotation = mat4(1); - vec4 pos = vec4(aPos, 1) * (mod(type, 2) == 1 ? do_rotation : no_rotation) * + vec4 pos = vec4(aPos, 1) * (mod(type >> 4, 2) == 1 ? billboard : (mod(type, 2) == 1 ? rotated : mat4(1))) * translate(aChunkOffset) * model; gl_Position = pos * projection; diff --git a/src/resources/texture/particle/blood.png b/src/resources/texture/particle/blood.png index 85d663a9053634f3257fcad302d870effebeaf1d..5e775025683d4aae3b33dd1bb6921318cc1f5de3 100644 GIT binary patch delta 1114 zcmcbs{eh#tGr-TCmrII^fq{Y7)59f*fq_8)ggKZP7#Jo@%rIkMU~J2Db`J1#c2+1T z%1_J8No8Qrm{>c}*5j~)%+dJht#u(XZxmilSlHFf8Yt+#)=5ySC2C^Jiv&ByMG4b`L)A@5g^Zl+&yWyewyyNf@r+uAI)zVm< zRp+VnX{>Vl`EN`8X|CtG=dw1QdG-B?K2Pe_r>g5N%ecsJN3?J-8lAe~ASkwIOV*MZ z9+&FQ9KQelhilQvC$|`skK1?!C|cHiu>Wy)?abK{<&&KZ7koXs&AzSSfnLDs8{fWf z%)Rc{aKN}=sp+4kS_XeTtClWw?Q58q`{2>8g(s^IJmtIaeTjG56A#9?S^r}AD(n~M zpJLd%-QoGF+tIc1%lRF;c6r@lU~fNAFmKa!n|pWf-MPI#{ng*U3_;h5W^L`&OFxwVD2A8L)6+$E}B&lKV5;uxZFp7mgK zzO*Yt!^QkG{d;rgmaaJwafM@z($+1SnqhGd7y|_Tf1dYXWw8n96ys89nsD#3+JqZh z_wJr|b7w){4*qo-YMw?xif@!Qyg@nzZX^viFo z%*7Ukypr9~z{azu?P{9w+bNKA0D<9%-@7h*6!PByRS8n&FDW$bJ zJ&peC&GRpXcttqhEcITxZ`~@nkjQzkUX{^-adj&uAuY5?J*Pp0zL_gAu^`k(X6Q}f#9joNb&Whyqx=lZw%E+DZpq?eI z^zHT6+Dj)nuYG=R_pW<}&sM#!u9%o={C>ax<=Xe$iY775mj!Y-%74ez$sMYFzxwJc z{u|8v?Dy)YtFRy2(|Fu)mda0E-~5!DGIFY(pROis*!E3`ue@x{6z=DX1eR`1cP;c@ zxzSMFaGleHb1!wQg_Y*!n0c*C?GdV)k^Q%*#-jvR@zmTmhMz2n|@`2#FY(@shpF4$qP?9}Joo=Wy5?^3qTGVOi0i_Pz0z-#S_ zWu;78rwmWD@edaT9s_qtXUr-wq@Q-iR*vU{F>XPjJ8bNO&@?kSGs2}dj^ z%?bGbf4`%BL8s@$Ic+Z{e`lNSt><}6VbmoHXKdS<8FK5zZ`FA-WFJa?sLhrQ?1 ze_NP-N6t37zcfeq*oMBiHF7Kh8qM4vY;tpUS1zhN^i1)8!Nu~vt%o1Vu=QW$W3HVKNoZE9-|{;J+3zFc39^}C6VdcRl2Era!2&Q@*qF9TGh?bSw}#xC9fi*v?_6I{)Y|X2i^XH2 zR@qtCf{9CRdG4Cs+s<;~eh$l5(S=Ed+Q}EbwNAgg zjI)|){nkZAXBK-kak|c8D`;k6jXbCMNPS0##Hp;SD_31#5I8w4f3I@NjPD1}7tL+t_Rr2=cywCSyE}qjcT1PPtA4R@`8@Wd+#3(u`0NjLDEIzJ z$=&?xnq5M#dG@9mvtA#XYw+1GT<`6*G7g7Ni!XDOsye-g$DSXVaR1 z#>rMYGjoip3sYj#FCO%NS7-QLF#pNj_{z!ejoc^intew1-ip`U`TG;so;_a{+anuk zVQ4CLv;Ash(zGDsvWc_4wpjgGIA!6RDOg^`clQ13^q`*wYKQf=iv-V`|K0L; zZRD+>cBh-&k$Gny{gj_u#J?E2+Pyb*rOmr8G#F-!EVFCl{jPS%Z@vCX_{Dkl zZI%zGPuLwX&uba$+8aXlYrXhqUcThE;?FZCmHdtiPbd9l72L&fQd+4*aFV&p3ndl# z>x=CjKb0lz;y5{bN%w@Bpw0HDrJ@uw_8GSx8NBD28J}OxjnO> zBpH<3ZM_VI8VopYSGM{uU^!^F+}(PI<&Xc9jb_LQOuF!QkE6R|Z^6op>nCUZQM}Z| zIRwL~E)H9a%WR_Xoj{Yna%DYi=CroINg16w1-Ypui3%0DIeEoa6}C!XbFK1^!3Zj%k|2Q z_413-^$jg8E%gnI^o@*ki&D~bi!1X=5-W7`ij_e|K+JGSElw`VEGWs$&r<-InV6Jc zT4JlD#HFC105u#Gx~>%^a0@_uu<^wuDf*rTCCMfgxdpBjCHh7N1{S&oM!H6p`pEh# zatnNY;kxsRp`n(i=v~r#I+1zA66a3A(aKG`a!A1`K3k4sjg+I zc_qromKNlc79-nPTAT_J0=qjWB~8B~7h*HA9(PaQ0Jt7dFs5hb7JzktR3ocQNrtN{ zC@snXt4T@LPt8fqP0cGQ);H8MMDb8XZUI~aR_}lf2M3guOMY@G$P`Z(TP2VKtWxrm zGgGXi?#H3UsO9M-bL?oj;^NLFn^O93R zMpfh%=w)W6SeYjqq?#t0Tk0AcnH%VuB%7J(CMBAt=$a*`T3A}7CZ`#injjhBUzC}i znU|P@>?)8^DVZr&$%)2^rYUAAx`rt!NxCNHCT6-xNhztirbdPa28Nb~Mkyu`*MI`n z$}zyxR>?@u0Bkr|Bp@fTBrU%v*H+0VGq1D)ArX?9n;Kk_2nsYqQ%fUb0|N^aQxg+I zBNH=(qOjDW;>`R!keP-CdWH~3rzBgsKEjtgSAxT7FanKr6!i- z7lq{K=h!NNT%cg2XJ`P<3ko)%RP9<(V&#*coCwMdV8y8^cHoQv&TdYL>H1)%;ifnim6YcfW&0QTCuOB3m*6)Cn_{?0L8XIQLp5CWAuJR;f6#EHwoj1qv{!Co*wjPHaB--vT%W~D?DibI?5g>b{X+_84 zu!S6=PHQi$YCgHt-f421%F&~N1-ceLHb;~e6kfY}FjRo0-eHc+T#?u%N8V3z>XvwK z@$TW?_q98J^9f|BE{n`fZdi6ZO-oSR_@=1gmoC09N-o7NY10g~Jw1Q)t*>VJQ78U5 z@?U9vU!G~Yp@M?Yd4t1SIV-Byn9KVk7^86*?v!lLnytv7xw{G^VP3$r682}K5+ws)WDxx!xzOIC)Tlz#9)KteCz+pP1ut)qW`%lBPeZ?E&d zsQdFr9}5Ns2F?PH$YKTtJ!KGPtXOJa#=yY9UgGKN%Kn5+fKy-c`M+P93=9mCC9V-A z&iT2Zz9|E^IhvZHP@Y+mp#bVig*WnvGcYiSdAc};a9mHGbNWQqfe-yJZ~wPHFCoFG XJcD)Z%*Kj?pivV~S3j3^P6