From e39f52d04163b029d38d20ac8e306194fc21e8eb Mon Sep 17 00:00:00 2001 From: Chris Cromer Date: Sat, 5 Oct 2019 23:41:14 -0300 Subject: [PATCH] Add gems and new end game spot Signed-off-by: Chris Cromer --- .idea/gradle.xml | 1 + .idea/misc.xml | 2 +- build.gradle | 5 +- src/main/java/cl/cromer/azaraka/Celda.java | 100 ++++++++++--- .../java/cl/cromer/azaraka/Constantes.java | 6 +- .../java/cl/cromer/azaraka/Escenario.java | 26 ++-- src/main/java/cl/cromer/azaraka/Lienzo.java | 31 +++- .../java/cl/cromer/azaraka/json/Json.java | 2 +- .../java/cl/cromer/azaraka/object/Chest.java | 42 +++++- .../java/cl/cromer/azaraka/object/Enemy.java | 12 ++ .../java/cl/cromer/azaraka/object/Gem.java | 139 +++++++++++++++++- .../java/cl/cromer/azaraka/object/Key.java | 2 +- .../java/cl/cromer/azaraka/object/Object.java | 70 ++++++++- .../java/cl/cromer/azaraka/object/Player.java | 101 ++++++++----- .../java/cl/cromer/azaraka/object/Portal.java | 26 +++- src/main/resources/img/gem/blue/0.png | Bin 0 -> 464 bytes src/main/resources/img/gem/blue/1.png | Bin 0 -> 447 bytes src/main/resources/img/gem/blue/2.png | Bin 0 -> 451 bytes src/main/resources/img/gem/blue/3.png | Bin 0 -> 408 bytes src/main/resources/img/gem/blue/4.png | Bin 0 -> 439 bytes src/main/resources/img/gem/blue/5.png | Bin 0 -> 458 bytes src/main/resources/img/gem/blue/6.png | Bin 0 -> 449 bytes src/main/resources/img/gem/gray/0.png | Bin 0 -> 463 bytes src/main/resources/img/gem/gray/1.png | Bin 0 -> 446 bytes src/main/resources/img/gem/gray/2.png | Bin 0 -> 452 bytes src/main/resources/img/gem/gray/3.png | Bin 0 -> 415 bytes src/main/resources/img/gem/gray/4.png | Bin 0 -> 441 bytes src/main/resources/img/gem/gray/5.png | Bin 0 -> 462 bytes src/main/resources/img/gem/gray/6.png | Bin 0 -> 450 bytes src/main/resources/img/gem/red/0.png | Bin 0 -> 465 bytes src/main/resources/img/gem/red/1.png | Bin 0 -> 445 bytes src/main/resources/img/gem/red/2.png | Bin 0 -> 451 bytes src/main/resources/img/gem/red/3.png | Bin 0 -> 406 bytes src/main/resources/img/gem/red/4.png | Bin 0 -> 439 bytes src/main/resources/img/gem/red/5.png | Bin 0 -> 456 bytes src/main/resources/img/gem/red/6.png | Bin 0 -> 447 bytes 36 files changed, 484 insertions(+), 81 deletions(-) create mode 100755 src/main/resources/img/gem/blue/0.png create mode 100755 src/main/resources/img/gem/blue/1.png create mode 100755 src/main/resources/img/gem/blue/2.png create mode 100755 src/main/resources/img/gem/blue/3.png create mode 100755 src/main/resources/img/gem/blue/4.png create mode 100755 src/main/resources/img/gem/blue/5.png create mode 100755 src/main/resources/img/gem/blue/6.png create mode 100755 src/main/resources/img/gem/gray/0.png create mode 100755 src/main/resources/img/gem/gray/1.png create mode 100755 src/main/resources/img/gem/gray/2.png create mode 100755 src/main/resources/img/gem/gray/3.png create mode 100755 src/main/resources/img/gem/gray/4.png create mode 100755 src/main/resources/img/gem/gray/5.png create mode 100755 src/main/resources/img/gem/gray/6.png create mode 100755 src/main/resources/img/gem/red/0.png create mode 100755 src/main/resources/img/gem/red/1.png create mode 100755 src/main/resources/img/gem/red/2.png create mode 100755 src/main/resources/img/gem/red/3.png create mode 100755 src/main/resources/img/gem/red/4.png create mode 100755 src/main/resources/img/gem/red/5.png create mode 100755 src/main/resources/img/gem/red/6.png diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 310e0f2..13a8247 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,5 +1,6 @@ + - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 3137788..6831dc3 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,7 @@ group 'cl.cromer.azaraka' version '1.0.0' sourceCompatibility = 1.8 +targetCompatibility = 1.8 mainClassName = "cl.cromer.azaraka.Main" @@ -37,7 +38,9 @@ dependencies { jar { manifest { attributes "Main-Class": "$mainClassName", - 'Class-Path': configurations.default.files.collect { "$it.name" }.join(' ') + 'Class-Path': configurations.default.files.collect { "$it.name" }.join(' '), + "Implementation-Title": "Gradle", + "Implementation-Version": "$gradle.gradleVersion" } // This adds the libs into the jar diff --git a/src/main/java/cl/cromer/azaraka/Celda.java b/src/main/java/cl/cromer/azaraka/Celda.java index ec4ed66..e4d704f 100644 --- a/src/main/java/cl/cromer/azaraka/Celda.java +++ b/src/main/java/cl/cromer/azaraka/Celda.java @@ -21,6 +21,8 @@ import javax.swing.*; import java.awt.*; import java.awt.image.BufferedImage; import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; /** * This class is a cell that will contain a game element such as a player, enemy, prize, etc @@ -43,17 +45,21 @@ public class Celda extends JComponent implements Constantes { */ private final int y; /** - * The textures to show in this cell + * A map containing the textures used in the cell, LinkedHashMap is used to maintain the order of images */ - private final ArrayList textures = new ArrayList<>(); - /** - * The texture numbers - */ - private final ArrayList textureNumbers = new ArrayList<>(); + private final LinkedHashMap textures = new LinkedHashMap<>(); /** * The object in the cell */ private Object object = null; + /** + * An object that doesn't collide and is drawn on top of the other sprites + */ + private Object objectOnTop = null; + /** + * An object that doesn't collide and is drawn below the other sprites + */ + private Object objectOnBottom = null; /** * Initialize the cell with its coordinates @@ -88,6 +94,51 @@ public class Celda extends JComponent implements Constantes { this.object = object; } + /** + * Get the top object + * + * @return Returns the top object + */ + public Object getObjectOnTop() { + return objectOnTop; + } + + /** + * Set a top object + * + * @param object The top object + */ + public void setObjectOnTop(Object object) { + this.objectOnTop = object; + } + + /** + * Get a bottom object + * + * @return Returns the bottom object + */ + public Object getObjectOnBottom() { + return objectOnBottom; + } + + /** + * Set a bottom object + * + * @param object The object + */ + public void setObjectOnBottom(Object object) { + this.objectOnBottom = object; + } + + /** + * Check if cell contains an object + * + * @return Returns true if it contains an object or false otherwise + */ + public boolean containsObject() { + return (object != null || objectOnTop != null || objectOnBottom != null); + } + /** * Get the x coordinate of the cell * @@ -113,25 +164,27 @@ public class Celda extends JComponent implements Constantes { * @param textureNumber The texture's number */ public void addTexture(BufferedImage texture, int textureNumber) { - textures.add(texture); - textureNumbers.add(textureNumber); + textures.put(textureNumber, texture); } /** - * Remove the texture that is on the top of the stack + * Remove the texture from the map */ - public void removeTopTexture() { - textures.remove(textures.size() - 1); - textureNumbers.remove(textureNumbers.size() - 1); + public void removeTexture(int texture) { + textures.remove(texture); } /** - * Get the numbers of the textures + * Get an array list of the texture numbers used * - * @return Returns an array list containing the texture numbers + * @return Returns an array list of texture numbers */ public ArrayList getTextureNumbers() { - return textureNumbers; + ArrayList arrayList = new ArrayList<>(); + for (Map.Entry entry : textures.entrySet()) { + arrayList.add(entry.getKey()); + } + return arrayList; } /** @@ -151,18 +204,27 @@ public class Celda extends JComponent implements Constantes { */ @Override public void update(Graphics g) { - // Don't replace with foreach because it can cause a race condition - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < textures.size(); i++) { - BufferedImage texture = textures.get(i); + // Draw the textures in the cell + for (Map.Entry entry : textures.entrySet()) { + BufferedImage texture = entry.getValue(); if (texture != null) { g.drawImage(texture, xPixels, yPixels, null); } } + // Draw the bottom sprite + if (objectOnBottom != null) { + objectOnBottom.drawAnimation(g, xPixels, yPixels); + } + // Draw a sprite in the cell if needed if (object != null) { object.drawAnimation(g, xPixels, yPixels); } + + // Draw the top sprite + if (objectOnTop != null) { + objectOnTop.drawAnimation(g, xPixels, yPixels); + } } } \ No newline at end of file diff --git a/src/main/java/cl/cromer/azaraka/Constantes.java b/src/main/java/cl/cromer/azaraka/Constantes.java index 46e5eeb..64c4443 100644 --- a/src/main/java/cl/cromer/azaraka/Constantes.java +++ b/src/main/java/cl/cromer/azaraka/Constantes.java @@ -203,7 +203,7 @@ public interface Constantes { /** * The global log level is used if the individual log levels are not */ - GLOBAL(Level.WARNING), + GLOBAL(Level.SEVERE), /** * The main log level */ @@ -256,6 +256,10 @@ public interface Constantes { * The json log level */ JSON(Level.INFO), + /** + * The gem log level + */ + GEM(Level.INFO), /** * The portal log level */ diff --git a/src/main/java/cl/cromer/azaraka/Escenario.java b/src/main/java/cl/cromer/azaraka/Escenario.java index 1d86847..0ff8e0d 100644 --- a/src/main/java/cl/cromer/azaraka/Escenario.java +++ b/src/main/java/cl/cromer/azaraka/Escenario.java @@ -190,14 +190,14 @@ public class Escenario extends JComponent implements Constantes { } random = randomCoordinates(); - celdas[random[0]][random[1]].setObject(new Portal(this, celdas[random[0]][random[1]])); - objectArrayList.add(celdas[random[0]][random[1]].getObject()); + celdas[random[0]][random[1]].setObjectOnBottom(new Portal(this, celdas[random[0]][random[1]])); + objectArrayList.add(celdas[random[0]][random[1]].getObjectOnBottom()); // Generate enough keys for the chests that will exist for (int i = 0; i < CHESTS; i++) { random = randomCoordinates(); - celdas[random[0]][random[1]].setObject(new Key(this, celdas[random[0]][random[1]])); - objectArrayList.add(celdas[random[0]][random[1]].getObject()); + celdas[random[0]][random[1]].setObjectOnBottom(new Key(this, celdas[random[0]][random[1]])); + objectArrayList.add(celdas[random[0]][random[1]].getObjectOnBottom()); } // Chests need to be last to make sure they are openable @@ -206,14 +206,14 @@ public class Escenario extends JComponent implements Constantes { int random_y = random(0, VERTICAL_CELLS - 1); // Don't put a chest if it can't be opened while (random_y + 1 == VERTICAL_CELLS || - celdas[random_x][random_y].getObject() != null || - celdas[random_x][random_y + 1].getObject() != null || - celdas[random_x][random_y - 1].getObject() != null) { + celdas[random_x][random_y].containsObject() || + celdas[random_x][random_y + 1].containsObject() || + celdas[random_x][random_y - 1].containsObject()) { random_x = random(0, HORIZONTAL_CELLS - 1); random_y = random(0, VERTICAL_CELLS - 1); } - celdas[random_x][random_y].setObject(new Chest(this, celdas[random_x][random_y])); - objectArrayList.add(celdas[random_x][random_y].getObject()); + celdas[random_x][random_y].setObjectOnBottom(new Chest(this, celdas[random_x][random_y])); + objectArrayList.add(celdas[random_x][random_y].getObjectOnBottom()); } return objectArrayList; @@ -228,7 +228,7 @@ public class Escenario extends JComponent implements Constantes { int[] random = new int[2]; random[0] = random(0, HORIZONTAL_CELLS - 1); random[1] = random(0, VERTICAL_CELLS - 1); - while (celdas[random[0]][random[1]].getObject() != null) { + while (celdas[random[0]][random[1]].containsObject()) { random[0] = random(0, HORIZONTAL_CELLS - 1); random[1] = random(0, VERTICAL_CELLS - 1); } @@ -486,16 +486,18 @@ public class Escenario extends JComponent implements Constantes { */ public void setDoorClosed(boolean doorClosed) { if (doorClosed && !isDoorClosed()) { + celdas[2][0].setObject(new Obstacle(this, celdas[2][0])); try { celdas[2][0].addTexture(textureSheet.getTexture(193), 193); } catch (SheetException e) { - e.printStackTrace(); + logger.warning(e.getMessage()); } this.doorClosed = true; } else if (!doorClosed && isDoorClosed()) { - celdas[2][0].removeTopTexture(); + celdas[2][0].removeTexture(193); + celdas[2][0].setObject(null); this.doorClosed = false; } } diff --git a/src/main/java/cl/cromer/azaraka/Lienzo.java b/src/main/java/cl/cromer/azaraka/Lienzo.java index 768a284..2d6a7d4 100644 --- a/src/main/java/cl/cromer/azaraka/Lienzo.java +++ b/src/main/java/cl/cromer/azaraka/Lienzo.java @@ -139,15 +139,24 @@ public class Lienzo extends Canvas implements Constantes { Enemy.Direction enemyDirection = Enemy.Direction.DOWN; + // Create the gems and later place them in 2 of the chests + ArrayList gems = new ArrayList<>(); + Gem lifeGem = new Gem(escenario, new Celda(0, 0, 0, 0)); + lifeGem.setType(Gem.Type.LIFE); + Gem deathGem = new Gem(escenario, new Celda(0, 0, 0, 0)); + deathGem.setType(Gem.Type.DEATH); + gems.add(lifeGem); + gems.add(deathGem); + ArrayList objectList = escenario.generateRandomObjects(); for (Object object : objectList) { - object.getCelda().setObject(object); if (object instanceof Player) { + object.getCelda().setObject(object); player = (Player) object; threads.put(object, new Thread(object)); } else if (object instanceof Enemy) { - ((Enemy) object).setDirection(enemyDirection); + object.getCelda().setObject(object); if (enemyDirection == Enemy.Direction.UP) { enemyDirection = Enemy.Direction.DOWN; } @@ -160,18 +169,30 @@ public class Lienzo extends Canvas implements Constantes { else { enemyDirection = Enemy.Direction.UP; } + ((Enemy) object).setDirection(enemyDirection); enemies.add((Enemy) object); threads.put(object, new Thread(object)); } else if (object instanceof Chest) { + object.getCelda().setObject(object); + if (gems.size() > 0) { + Gem gem = gems.get(0); + // Place the gem in the cell above the chest, but don't add it to object2 until we are ready to draw it + gem.setCelda(escenario.getCeldas()[object.getCelda().getX()][object.getCelda().getY() - 1]); + threads.put(gem, new Thread(gem)); + ((Chest) object).setGem(gem); + gems.remove(gem); + } chests.add((Chest) object); threads.put(object, new Thread(object)); } else if (object instanceof Key) { + object.getCelda().setObjectOnBottom(object); keys.add((Key) object); threads.put(object, new Thread(object)); } else if (object instanceof Portal) { + object.getCelda().setObjectOnBottom(object); portal = (Portal) object; threads.put(object, new Thread(object)); } @@ -227,6 +248,12 @@ public class Lienzo extends Canvas implements Constantes { } } + ArrayList gems = player.getInventoryGems(); + for (Gem gem : gems) { + gem.drawAnimation(graphicBuffer, xKey, 8); + xKey = xKey + 27; + } + if (player != null) { int health = player.getHealth(); if (health == 0) { diff --git a/src/main/java/cl/cromer/azaraka/json/Json.java b/src/main/java/cl/cromer/azaraka/json/Json.java index dbb71bd..3c3cec8 100644 --- a/src/main/java/cl/cromer/azaraka/json/Json.java +++ b/src/main/java/cl/cromer/azaraka/json/Json.java @@ -79,7 +79,7 @@ public class Json implements Constantes { Gson gson = gsonBuilder.create(); String json = gson.toJson(cells); - File file = new File("res/scene.json"); + File file = new File("src/main/resources/scene.json"); try { FileOutputStream fileOutputStream = new FileOutputStream(file); fileOutputStream.write(json.getBytes()); diff --git a/src/main/java/cl/cromer/azaraka/object/Chest.java b/src/main/java/cl/cromer/azaraka/object/Chest.java index bee500b..50c121b 100644 --- a/src/main/java/cl/cromer/azaraka/object/Chest.java +++ b/src/main/java/cl/cromer/azaraka/object/Chest.java @@ -37,6 +37,14 @@ public class Chest extends Object implements Constantes { * The open chest sound */ private Sound sound; + /** + * The gem contained in the chest + */ + private Gem gem = null; + /** + * The number of loops before the gem should move to inventory + */ + private int gemLoops = 3; /** * Initialize the chest @@ -118,6 +126,24 @@ public class Chest extends Object implements Constantes { } } + /** + * Get the gem from the chest + * + * @return The gem in the chest + */ + public Gem getGem() { + return gem; + } + + /** + * Put a gem in the chest + * + * @param gem The gem + */ + public void setGem(Gem gem) { + this.gem = gem; + } + /** * Play the chest opening sound */ @@ -160,7 +186,21 @@ public class Chest extends Object implements Constantes { getLogger().info(e.getMessage()); } synchronized (this) { - if (state == State.OPENING) { + if (state == State.OPENED) { + if (gemLoops > 0) { + gemLoops--; + } + else if (gemLoops == 0) { + gem.getCelda().setObjectOnTop(null); + gem.setYScale(24); + gem.setXScale(24); + gem.setUseOffset(false); + getEscenario().getCanvas().getPlayer().addInventory(gem); + getEscenario().getCanvas().getPortal().setState(Portal.State.ACTIVE); + gemLoops--; + } + } + else if (state == State.OPENING) { animate(); getEscenario().getCanvas().repaint(); } diff --git a/src/main/java/cl/cromer/azaraka/object/Enemy.java b/src/main/java/cl/cromer/azaraka/object/Enemy.java index c8ec09e..a71f7a5 100644 --- a/src/main/java/cl/cromer/azaraka/object/Enemy.java +++ b/src/main/java/cl/cromer/azaraka/object/Enemy.java @@ -101,6 +101,18 @@ public class Enemy extends Object implements Constantes { */ public void setDirection(Direction direction) { this.direction = direction; + if (direction == Direction.UP) { + getAnimation().setCurrentDirection(Animation.Direction.UP); + } + else if (direction == Direction.DOWN) { + getAnimation().setCurrentDirection(Animation.Direction.DOWN); + } + else if (direction == Direction.LEFT) { + getAnimation().setCurrentDirection(Animation.Direction.LEFT); + } + else if (direction == Direction.RIGHT) { + getAnimation().setCurrentDirection(Animation.Direction.RIGHT); + } } /** diff --git a/src/main/java/cl/cromer/azaraka/object/Gem.java b/src/main/java/cl/cromer/azaraka/object/Gem.java index 8c8d876..185f38f 100644 --- a/src/main/java/cl/cromer/azaraka/object/Gem.java +++ b/src/main/java/cl/cromer/azaraka/object/Gem.java @@ -17,15 +17,29 @@ package cl.cromer.azaraka.object; import cl.cromer.azaraka.Celda; import cl.cromer.azaraka.Escenario; +import cl.cromer.azaraka.sprite.Animation; +import cl.cromer.azaraka.sprite.AnimationException; /** * This class contains the gem */ public class Gem extends Object { + /** + * The type of gem + */ + private Type type = null; /** * The current state of the gem */ private State state = State.TAINTED; + /** + * + */ + private Animation taintedAnimation; + /** + * The animation to use when the gem is purified + */ + private Animation purifiedAnimation; /** * Initialize the gem object @@ -35,12 +49,135 @@ public class Gem extends Object { */ public Gem(Escenario escenario, Celda celda) { super(escenario, celda); + setLogger(getLogger(this.getClass(), LogLevel.GEM)); + loadGemAnimation(null); + setAnimation(taintedAnimation); + } + + /** + * Load the gem animations + */ + private void loadGemAnimation(Type type) { + if (type == null) { + taintedAnimation = new Animation(); + for (int i = 0; i <= 6; i++) { + String string = i + ".png"; + taintedAnimation.addImage(Animation.Direction.NONE, "/img/gem/gray/" + string); + } + taintedAnimation.setYOffset(32); + } + else { + switch (type) { + case LIFE: + purifiedAnimation = new Animation(); + for (int i = 0; i <= 6; i++) { + String string = i + ".png"; + purifiedAnimation.addImage(Animation.Direction.NONE, "/img/gem/blue/" + string); + } + break; + case DEATH: + purifiedAnimation = new Animation(); + for (int i = 0; i <= 6; i++) { + String string = i + ".png"; + purifiedAnimation.addImage(Animation.Direction.NONE, "/img/gem/red/" + string); + } + break; + } + } + } + + /** + * Set the gem type + * + * @param type The type of gem + */ + public void setType(Type type) { + this.type = type; + loadGemAnimation(type); + } + + /** + * Get the current state of the gem + * + * @return Returns the state of the gem + */ + public State getState() { + return state; + } + + /** + * Set the state of the gem + * + * @param state The new state + */ + public void setState(State state) { + this.state = state; + switch (state) { + case PURIFIED: + try { + purifiedAnimation.setCurrentFrame(0); + } + catch (AnimationException e) { + getLogger().warning(e.getMessage()); + } + setAnimation(purifiedAnimation); + break; + case TAINTED: + try { + taintedAnimation.setCurrentFrame(0); + } + catch (AnimationException e) { + getLogger().warning(e.getMessage()); + } + setAnimation(taintedAnimation); + break; + } + } + + /** + * This method animates the gem + */ + private void animate() { + try { + getAnimation().getNextFrame(); + } + catch (AnimationException e) { + getLogger().warning(e.getMessage()); + } + } + + /** + * This method is run when the thread starts + */ + @Override + public void run() { + super.run(); + while (getActive()) { + try { + Thread.sleep(60); + } + catch (InterruptedException e) { + getLogger().info(e.getMessage()); + } + synchronized (this) { + animate(); + getEscenario().getCanvas().repaint(); + } + } + } + + /** + * The type of gem + */ + public enum Type { + LIFE, + DEATH } /** * The possible states of the gem */ - private enum State { + public enum State { /** * The gem is tainted */ diff --git a/src/main/java/cl/cromer/azaraka/object/Key.java b/src/main/java/cl/cromer/azaraka/object/Key.java index 475600d..1df10fc 100644 --- a/src/main/java/cl/cromer/azaraka/object/Key.java +++ b/src/main/java/cl/cromer/azaraka/object/Key.java @@ -118,7 +118,7 @@ public class Key extends Object implements Constantes { */ public void getKey() { // Remove the key from the cell - getCelda().setObject(null); + getCelda().setObjectOnBottom(null); setState(State.HELD); } diff --git a/src/main/java/cl/cromer/azaraka/object/Object.java b/src/main/java/cl/cromer/azaraka/object/Object.java index 4a1c2c2..2161cef 100644 --- a/src/main/java/cl/cromer/azaraka/object/Object.java +++ b/src/main/java/cl/cromer/azaraka/object/Object.java @@ -16,6 +16,7 @@ package cl.cromer.azaraka.object; import cl.cromer.azaraka.Celda; +import cl.cromer.azaraka.Constantes; import cl.cromer.azaraka.Escenario; import cl.cromer.azaraka.sprite.Animation; import cl.cromer.azaraka.sprite.AnimationException; @@ -23,12 +24,13 @@ import cl.cromer.azaraka.sprite.Sheet; import cl.cromer.azaraka.sprite.SheetException; import java.awt.*; +import java.awt.image.BufferedImage; import java.util.logging.Logger; /** * All game objects extend this class */ -public class Object implements Runnable { +public class Object implements Runnable, Constantes { /** * The scene the object is in */ @@ -61,6 +63,14 @@ public class Object implements Runnable { * Whether or not the run loop of the object is active */ private boolean active; + /** + * x scale + */ + private int xScale = 0; + /** + * y scale + */ + private int yScale = 0; /** * Initialize the object @@ -111,6 +121,24 @@ public class Object implements Runnable { this.y = y; } + /** + * Scale the image to x pixels + * + * @param x The amount of pixels to scale + */ + public void setXScale(int x) { + this.xScale = x; + } + + /** + * Scale the image to y pixels + * + * @param y The amount of pixels to scale + */ + public void setYScale(int y) { + this.yScale = y; + } + /** * Get the scene the object is in * @@ -134,7 +162,7 @@ public class Object implements Runnable { * * @param celda The cell */ - protected void setCelda(Celda celda) { + public void setCelda(Celda celda) { this.celda = celda; } @@ -208,11 +236,45 @@ public class Object implements Runnable { public void drawAnimation(Graphics graphics, int x, int y) { try { if (animation != null && animation.getFrame() != null) { + BufferedImage frame = animation.getFrame(); + if (frame == null) { + // No animation, so don't draw anything + return; + } + + int xOffset = animation.getXOffset(); + int yOffset = animation.getYOffset(); + + // Check if scale is needed + if (xScale != 0 || yScale != 0) { + if (xScale == 0) { + xScale = frame.getWidth(); + } + else if (yScale == 0) { + yScale = frame.getHeight(); + } + frame = Animation.scaleImage(frame, xScale, yScale); + + if (frame.getWidth() == CELL_PIXELS) { + xOffset = 0; + } + else { + xOffset = (CELL_PIXELS - frame.getWidth()) / 2; + } + + if (frame.getHeight() == CELL_PIXELS) { + yOffset = 0; + } + else { + yOffset = (CELL_PIXELS - frame.getHeight()) / 2; + } + } + if (useOffset) { - graphics.drawImage(animation.getFrame(), x + animation.getXOffset(), y + animation.getYOffset(), null); + graphics.drawImage(frame, x + xOffset, y + yOffset, null); } else { - graphics.drawImage(animation.getFrame(), x, y, null); + graphics.drawImage(frame, x, y, null); } } } diff --git a/src/main/java/cl/cromer/azaraka/object/Player.java b/src/main/java/cl/cromer/azaraka/object/Player.java index 2439d7b..08c443c 100644 --- a/src/main/java/cl/cromer/azaraka/object/Player.java +++ b/src/main/java/cl/cromer/azaraka/object/Player.java @@ -67,7 +67,17 @@ public class Player extends Object implements Constantes { */ public void keyPressed(KeyEvent event) { if (!getEscenario().isDoorClosed()) { - getEscenario().setDoorClosed(true); + ArrayList gems = getInventoryGems(); + if (gems.size() < 2) { + getEscenario().setDoorClosed(true); + } + else { + for (Gem gem : gems) { + if (gem.getState() == Gem.State.TAINTED) { + getEscenario().setDoorClosed(true); + } + } + } } switch (event.getKeyCode()) { case KeyEvent.VK_UP: @@ -95,10 +105,14 @@ public class Player extends Object implements Constantes { int x = getX(); int y = getY(); getLogger().info("Up key pressed"); - if (y > 0) { + if (x == 2 && y == 0) { + getEscenario().getCanvas().win(); + } + else if (y > 0) { Object type = getEscenario().getCeldas()[x][y - 1].getObject(); - if (type == null || type instanceof Key) { - if (type != null) { + if (type == null) { + Object typeBottom = getEscenario().getCeldas()[x][y - 1].getObjectOnBottom(); + if (typeBottom instanceof Key) { for (Key key : getEscenario().getCanvas().getKeys()) { if (key.checkPosition(x, y - 1)) { // Get the key @@ -107,6 +121,9 @@ public class Player extends Object implements Constantes { } } } + else if (typeBottom instanceof Portal) { + getEscenario().getCanvas().getPortal().purifyGems(); + } getCelda().setObject(null); setCelda(getEscenario().getCeldas()[x][y - 1]); @@ -123,9 +140,6 @@ public class Player extends Object implements Constantes { setY(getY() - 1); } - else if (type instanceof Portal && getEscenario().getCanvas().getPortal().getState() == Portal.State.ACTIVE) { - getEscenario().getCanvas().win(); - } else { if (changeDirection(Animation.Direction.UP)) { try { @@ -156,10 +170,11 @@ public class Player extends Object implements Constantes { int x = getX(); int y = getY(); getLogger().info("Down key pressed"); - Object type = getEscenario().getCeldas()[x][y + 1].getObject(); if (y < (VERTICAL_CELLS - 1)) { - if (type == null || type instanceof Key) { - if (type != null) { + Object type = getEscenario().getCeldas()[x][y + 1].getObject(); + if (type == null) { + Object typeBottom = getEscenario().getCeldas()[x][y + 1].getObjectOnBottom(); + if (typeBottom instanceof Key) { for (Key key : getEscenario().getCanvas().getKeys()) { if (key.checkPosition(x, y + 1)) { // Get the key @@ -168,6 +183,9 @@ public class Player extends Object implements Constantes { } } } + else if (typeBottom instanceof Portal) { + getEscenario().getCanvas().getPortal().purifyGems(); + } getCelda().setObject(null); setCelda(getEscenario().getCeldas()[x][y + 1]); @@ -184,9 +202,6 @@ public class Player extends Object implements Constantes { setY(getY() + 1); } - else if (type instanceof Portal && getEscenario().getCanvas().getPortal().getState() == Portal.State.ACTIVE) { - getEscenario().getCanvas().win(); - } else { if (changeDirection(Animation.Direction.DOWN)) { try { @@ -219,8 +234,9 @@ public class Player extends Object implements Constantes { getLogger().info("Left key pressed"); if (x > 0) { Object type = getEscenario().getCeldas()[x - 1][y].getObject(); - if (type == null || type instanceof Key) { - if (type != null) { + if (type == null) { + Object typeBottom = getEscenario().getCeldas()[x - 1][y].getObjectOnBottom(); + if (typeBottom instanceof Key) { for (Key key : getEscenario().getCanvas().getKeys()) { if (key.checkPosition(x - 1, y)) { // Get the key @@ -229,6 +245,9 @@ public class Player extends Object implements Constantes { } } } + else if (typeBottom instanceof Portal) { + getEscenario().getCanvas().getPortal().purifyGems(); + } getCelda().setObject(null); setCelda(getEscenario().getCeldas()[x - 1][y]); @@ -245,9 +264,6 @@ public class Player extends Object implements Constantes { setX(getX() - 1); } - else if (type instanceof Portal && getEscenario().getCanvas().getPortal().getState() == Portal.State.ACTIVE) { - getEscenario().getCanvas().win(); - } else { if (changeDirection(Animation.Direction.LEFT)) { try { @@ -278,10 +294,11 @@ public class Player extends Object implements Constantes { int x = getX(); int y = getY(); getLogger().info("Right key pressed"); - Object type = getEscenario().getCeldas()[x + 1][y].getObject(); if (x < (HORIZONTAL_CELLS - 1)) { - if (type == null || type instanceof Key) { - if (type != null) { + Object type = getEscenario().getCeldas()[x + 1][y].getObject(); + if (type == null) { + Object typeBottom = getEscenario().getCeldas()[x + 1][y].getObjectOnBottom(); + if (typeBottom instanceof Key) { for (Key key : getEscenario().getCanvas().getKeys()) { if (key.checkPosition(x + 1, y)) { // Get the key @@ -290,6 +307,9 @@ public class Player extends Object implements Constantes { } } } + else if (typeBottom instanceof Portal) { + getEscenario().getCanvas().getPortal().purifyGems(); + } getCelda().setObject(null); setCelda(getEscenario().getCeldas()[x + 1][y]); @@ -306,9 +326,6 @@ public class Player extends Object implements Constantes { setX(getX() + 1); } - else if (type instanceof Portal && getEscenario().getCanvas().getPortal().getState() == Portal.State.ACTIVE) { - getEscenario().getCanvas().win(); - } else { if (changeDirection(Animation.Direction.RIGHT)) { try { @@ -377,22 +394,16 @@ public class Player extends Object implements Constantes { gainHealth(2); - int openedChests = 0; for (Chest chest : getEscenario().getCanvas().getChests()) { if (chest.checkPosition(x, y - 1)) { if (chest.getState() == Chest.State.CLOSED) { chest.setState(Chest.State.OPENING); + Gem gem = chest.getGem(); + gem.getCelda().setObjectOnTop(gem); useKey(); + break; } } - if (chest.getState() == Chest.State.OPENED || chest.getState() == Chest.State.OPENING) { - openedChests++; - } - } - - // All chests are opened, activate portal - if (openedChests == getEscenario().getCanvas().getChests().size()) { - getEscenario().getCanvas().getPortal().setState(Portal.State.ACTIVE); } } } @@ -436,6 +447,30 @@ public class Player extends Object implements Constantes { return health; } + /** + * Add an object to player inventory + * + * @param object The object to add + */ + public void addInventory(Object object) { + carrying.add(object); + } + + /** + * Get the gems the player has + * + * @return Returns an array of the gems the player is carrying + */ + public ArrayList getInventoryGems() { + ArrayList gems = new ArrayList<>(); + for (Object object : carrying) { + if (object instanceof Gem) { + gems.add((Gem) object); + } + } + return gems; + } + /** * Lose a variable amount of health * diff --git a/src/main/java/cl/cromer/azaraka/object/Portal.java b/src/main/java/cl/cromer/azaraka/object/Portal.java index 52dab4c..1ea4f54 100644 --- a/src/main/java/cl/cromer/azaraka/object/Portal.java +++ b/src/main/java/cl/cromer/azaraka/object/Portal.java @@ -21,6 +21,8 @@ import cl.cromer.azaraka.Escenario; import cl.cromer.azaraka.sprite.Animation; import cl.cromer.azaraka.sprite.AnimationException; +import java.util.ArrayList; + /** * This class handles the portal functionality */ @@ -47,15 +49,15 @@ public class Portal extends Object implements Constantes { public Portal(Escenario escenario, Celda celda) { super(escenario, celda); setLogger(getLogger(this.getClass(), LogLevel.PORTAL)); - loadPortalAnimation(); + loadPortalAnimations(); } /** * Load the portal animation */ - private void loadPortalAnimation() { + private void loadPortalAnimations() { activeAnimation = new Animation(); - for (int i = 0; i < 120; i++) { + for (int i = 0; i <= 119; i++) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(i); while (stringBuilder.length() < 3) { @@ -66,7 +68,7 @@ public class Portal extends Object implements Constantes { } inactiveAnimation = new Animation(); - for (int i = 0; i < 120; i++) { + for (int i = 0; i <= 119; i++) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(i); while (stringBuilder.length() < 3) { @@ -79,6 +81,22 @@ public class Portal extends Object implements Constantes { setAnimation(inactiveAnimation); } + /** + * Purify the gems the player is carrying + */ + public void purifyGems() { + if (state == State.ACTIVE) { + ArrayList gems = getEscenario().getCanvas().getPlayer().getInventoryGems(); + for (Gem gem : gems) { + gem.setState(Gem.State.PURIFIED); + } + setState(State.INACTIVE); + if (gems.size() == 2) { + getEscenario().setDoorClosed(false); + } + } + } + /** * This method animates the portal */ diff --git a/src/main/resources/img/gem/blue/0.png b/src/main/resources/img/gem/blue/0.png new file mode 100755 index 0000000000000000000000000000000000000000..9eb4f12f8879d0b63145c38772148bba07e7f69b GIT binary patch literal 464 zcmV;>0WbcEP)V;BzIb5;1Tj?cYZU0B!o`?P5bwgFgP9IIK?d8 zOs22naT^mbNfl;c`}o_u-@7VjW$;*nU=zS#1kM^UXa1L*g3vAG0600!@v1AygYa6=DJ`I6?Vdac zs|7E8<=Q=25V{2oAn9w@?#Y7C-wZ2#(1q9{=L~gr! zVhAyvYjT!`ke>_8mk(a%mz)KNt=(Y>0rS1{=f#a1g(odusjMerqD z9Ne6wAP9ns;7~;95__sQ+??E-Yx<|vAqNhjKl%Uj-II`})a$?L_->X)XHU2m;*INB z@j9L6I>C*a2c_N9@9^RF<-%K|$Az(mg8&0-K(>VP??wnrj&K}qh35c-5W4o<@TDO3 zuink@6#^6$039SDV%pTHYXDILvc`~Xy{DmpDuDI!^#E%H`15x_-^d5*P5M619OStK z7#uw$Os*dT8ylj@EpXi?4OHeMJnih=mJoUqEC3pa@39__0Pxzy(gYfx!6kC-CpQ4odaZLB1a6h1@R2zEX~1WVf#HnFf01bYLP zHr9fW!WWQ$A_UUd1rgH3Ju4&E$^2wD*J!Z^hRZH{_k1(wE=$ztKk8(>p#H@()@53= z`MhosYAx;DEU0((Wq!~ZmDTP)mBkRnGi6D@05*m|=P<|1HS;O}l_AoxOY@8XHUt1H z^SS^Yga+`vi4C}nmuyrjfYrU60InsZtY``=0NTN80JaM7=Vu_@P~}UI`#e(@VE^jn zM+{bBbJV>CFB)0#r`_WR>g=5_QwSLY?r~IFOkA}93Ug{5-j)z-rWtvF8h|vvp1j-t z139J?hC!ipiAIm?5P z0MdNz%p+M4@*JeOSI#_=2Epe*cE9o1Eol%O1Dj(4_tRf6XGsX20*rBediR!Z?3N@1 tI>*`_PN`UyZitE literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/blue/3.png b/src/main/resources/img/gem/blue/3.png new file mode 100755 index 0000000000000000000000000000000000000000..a37cafef1b171b01631d4f2f49336a22cc7b4835 GIT binary patch literal 408 zcmV;J0cZY+P)R(8JXtYBo2p0g}+!*c)ApzuW&gBAuUxf)EcR0xg z!2zUjtQqfa{^2AWgt`rU-(iG5A#5>bnIH&&w@35MDaY7#(m|*IYIB4aZq6k^py@}O zb8!d)ph-;Dm-3r)aR>?@&X38T+pma2d^NmM*?IuL3h}rNrZffs0000K@^4OZBhyeXm6JnNN1f&d+UI;ZJr?MR#5YU@rs!P;fMu z9~3|#EI?kA3uN&@TmV4#El*$zfG`4JQKtZ%PoNV36zd#-);l$(P+ePq3}SG0o7ql; zwo}pRpqOt$yoTnwKLA$%IJvlYJ^(GEX2F literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/blue/5.png b/src/main/resources/img/gem/blue/5.png new file mode 100755 index 0000000000000000000000000000000000000000..a1f21ec487a25803664962d89be28b1d330d3e52 GIT binary patch literal 458 zcmV;*0X6=KP)R9Hvtma$3$K@f)bZR`{h&^Hj$TG&V)L9A`H)WS!wGiYfq zViAPo1wtTIK(Mh0B3u*iU;Sk=*}IwZ5{;N2hD-LA`M;f+yCj6QR#6)_S=c)th2zI; z`ZSr;j)(DU$gh^|hhV;aI>L3j-|WN$jl)3KS83OmRQ_T(WHD|7Nn>8dK}gbkd)j?r3JN zCffx5-fUvoz~1oA#RSjfy!o8q38TQ#>(-jUT*h8)%?pukw4KXvDo_R>7exPVX~)jP zi*{cGLYsgMFVnpbfJ3MN3hAHOIRQR?bdAmYdzO6@$OMpwuLh_b;NQ;xZQ=#_543_= z0X{n?APo@yIgk0&G^+soH7xG|x#qG4puxS2S9|%+i-?NE0Kodw;>Ud@*CG* r_C;2S0CC*=un-6zJbYFcUMseCx-ysDu>F4p00000NkvXXu0mjfROQAr literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/gray/0.png b/src/main/resources/img/gem/gray/0.png new file mode 100755 index 0000000000000000000000000000000000000000..aba587ea3e03702b646d1404eac562268f90ca4f GIT binary patch literal 463 zcmV;=0WkiFP)pzugfmVb8Vo3Uhv(CNQv`tcdAM>8B3nA7|5 z;%h!{V*)0r!YmwLO-l2v-eozvgJB7RO#p)tIBUd^&$AFPKNvpX7@0Q(s04x9k-VvS zZ3qD1AvSLlz=F^K%ZJ2Bh8DFt5rCIo$mJRX^5`|Gb|L`l-FE}|4U9m=oWGSoE+wO)s^HycrECZ7ErQwPacHT zf|tH>?Vcl-}5#RR848hHQ!002ovPDHLk FV1mg|#rpsN literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/gray/1.png b/src/main/resources/img/gem/gray/1.png new file mode 100755 index 0000000000000000000000000000000000000000..97419b5bc3b909c6985a81d767f08048544cfbb7 GIT binary patch literal 446 zcmV;v0YUzWP)3df82y7=(JMHYnDf`l;)!nf;QY+V+FZo!(qbM2O55LOF3 zedpRO#UQu^o_^=rEyW;M1&07*qoM6N<$g5Z0<`2YX_ literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/gray/2.png b/src/main/resources/img/gem/gray/2.png new file mode 100755 index 0000000000000000000000000000000000000000..fa9c2f47614d3db43c7cb3f5a4cd8145d63e4245 GIT binary patch literal 452 zcmV;#0XzPQP)Q4odad2B7j#=i+GWP6s2ble|90 z9J?hC!iu4oIm?5P z0MdNs%p+M4@*L#3*UmhW2Epe*c0c#nEol%O1Dj(4FP~rK%~=wHrvPJIU)?>F8@nY5 ufzGk+9G+9PM%&9sLL8h87H8h7w7vmV(Phz$Cwp)J0000N8KyBHEMadUu;2S84f+uUC7_rD-RPjb@GeDwUz{Yp!gH99w4U5~Qi!xYE;``35r zpWQCi85|UjBYZb|shEH`pynWmkZ@lBZwTLT3BW%mL1^Je2G~~shB!pTMe{(zzj z%5$deQvm)xjseiiZwm;zC$wM}fHnmlK3}fQFC;*8FZ*5)J^-@0H9QnT1Bh-eiDx!w5e>xMJ+GK@fm!kLIh*Hom!(34#Og<_J%2 zE~P=B=~0_YNeBXTfP3At?NQ@>Kk}J&7d5Ny8h}tzUfS>AIxp6#f7J002ov JPDHLkV1jPMubcn? literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/gray/4.png b/src/main/resources/img/gem/gray/4.png new file mode 100755 index 0000000000000000000000000000000000000000..83fecf6d7ebeb5fe4f2a53df79ed9e612bd6b8a7 GIT binary patch literal 441 zcmV;q0Y?6bP)9C2!^}AL8Bhe{f1jfuZ0EQr6kY`&2!Zg6;Uv2AIskt{000FK z4#ztMPzVc<7v%z3yb~7y(0$7j_ys^10kEi3fX*k-2>^<94nXUj8dIpQEkFiw)Ei{B z6QS)?bUG;Jn-H&|x$XzR6#)8!OXma7B5D?l**11Qe_d?9jyJ({2EiLM6@VFi`3FfM zIEh$*M&V@;p-F@QDuq*3AP|D5!Uuq0oO}EDj1Q+`w}Sv>og;j6J{0Gdh2Rtv!Z-d# zovQ$Fa(q?_p#VubS6K*d1*|t<(#}Fgz4=PKO}0x*1ZeTOe=s7x~{ jM62PHLbMvbS89C&FKOV=Ga-sG00000NkvXXu0mjf&Ni_I literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/gray/5.png b/src/main/resources/img/gem/gray/5.png new file mode 100755 index 0000000000000000000000000000000000000000..22d252889fd1321ea9050dc99705b66262504bd6 GIT binary patch literal 462 zcmV;<0WtoGP)B?*?tJ->&;VKd$V%~F~Lfs83SuN0Lj2!`CYENAgY>ch#Y_mqTCeA zPV-R%m=I@IH?ikc&8q-#PZ9t(Wm0Rf>c#+oEd38uU;+VP^uGaqy#=_*gsG{u5>}05 z1?FdpuL|gjc&dTK{LKBSNCK=jcH$fmLe9jnWD-iYEeDG|=bh^Tf0&1*Vxtd2R>1>- zmRRPO_mBDC13)Y;tOWpQ2+Q0WTGN~=jTvqT3&3qoX$T}8)(TmEZI1Pe+dIEGr6F=v zkf%?b9Ofm2WtWx*f=0E=o|6TU0vMS&B_Z@Yu+L*NrvSi5lvU8D|MtLn1&HNhvk!s* zF#r5pXQinp34s>y@!LCYjo+M-?ST2#{!x63PW31C4wdL>WnSH8>Hq)$07*qoM6N<$ Ef~j@II{*Lx literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/gray/6.png b/src/main/resources/img/gem/gray/6.png new file mode 100755 index 0000000000000000000000000000000000000000..d1507f9f6c768a5c3ccf09646f523b2454176dbb GIT binary patch literal 450 zcmV;z0X_bSP)o^%w_D=*1QnKM%%dzrvgm?DnY!YnH@V1 zuiJec2yFs3yh-;y01lx7D5U>p=LGop(KR;n?`8H)pcFtJz7C*ufSsQK+QbX;A7};3 z0(^E(KpG(Ya~1QcX;uOFYgpa`a?NE8K!aPZ-na6d7ZDYS0f6=F>)U-H0pg^0ZH^E6 zK~tcE7!*z_!cB5G+Jf*+L3SS$P6~l=bzeK2q(RVMQMLQvaFPb0O@VhGEu17Fv?)*^ zg#+g8aR2}S07*qoM6N<$g6m|ySpWb4 literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/red/0.png b/src/main/resources/img/gem/red/0.png new file mode 100755 index 0000000000000000000000000000000000000000..d7a2a664d84770f717830a1f89f5fdc4acd89628 GIT binary patch literal 465 zcmV;?0WSWDP)wzk6ds^& zz{bSZ5z$BJmZj=espO70+@0_2C`ns}oOH6%+Wx=)cN9A})PJk(^4S${cQEd-^K7?T zulr~R9T(3tS3KRK_}9rf_I5l%5ZDA@usxj8UQj+KAy9mF4I>xt2+#-uV+ZFQ#alxF z089$Rdj!xRD8S!DFI2Ty)N3;U%YB`I|4je}P1i)y;Q-p%4*&-RSRVTwpp(Rgbb+>E zN<^-+09XpLag&sD1)QZxx&m+v;B#{29?vG~QQ&i)F{ohUn7H___;?RL$ECQ-PcLf- zjk*7g7vDreFb>FKl_lo5ehRCC4CW3P}Lr9K_+M8_gMj#Wl-jOc z1rT0zpU!1b2!FYt`1E3b&Se3_{O0}_q7>h}JRDAZsJ`n5P>vRIWN%QX00000NkvXX Hu0mjfV-U)V literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/red/1.png b/src/main/resources/img/gem/red/1.png new file mode 100755 index 0000000000000000000000000000000000000000..3751179e62c4ed75222b056739734eb8f5d202d8 GIT binary patch literal 445 zcmV;u0Yd(XP)l#Uh5 zOl(=IY>|TP#@6%odwrx%3q!7SRn>}p&%22dVq;1ktV@`0(Qa zbSVG{iXpvW)2Pk>)CR&DKxU?UyZI>8tuIRey=x5iIS zZUfqfYm+nJNt@|FG2exougj|lk!-RK02BzX;cia^fVKK#oxhGL1RDVDXaIx4DMarq z1X~yipoaejLTy2s-aeSkdZ~v8Nf2raeHg;HaJ#O>rV^W|gclZ_erWBII0&%?oPKES zlGrA&1(^Qe+9h!ip@jfI!;5N{ltJ_@#OkZv3#?hZjUOI_5Oiwa#O&IASF~fwL?LK+ n^ZIoB@SqL-cAPqV(%AX|UtAb7m#nJw00000NkvXXu0mjf5)#D6 literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/red/2.png b/src/main/resources/img/gem/red/2.png new file mode 100755 index 0000000000000000000000000000000000000000..35a5ee34f03f242cfd3388d29417b552e39779e3 GIT binary patch literal 451 zcmV;!0X+VRP)zO%%}qH62K_xd0_6 zNI`)@MU80OMD8qwo%L>5X6Gk%R)NA#8dX%aP(5DYb)DAdYSp&@ zYb!MmQ>bpvy!qR~k*{VvYAZmvciJL=2CNO?J4bW3IcMGlpff}|_RPE-fDC~EqIpjM z4T1ysSr`pM37hOxEP&WOodDhw$gGSh~L@;BG6hIDunpew({6Fvz zqlpR;V-NuZbDUhgnA?9u4k9zrVn_^A5Hxqsbn_?^LW+Uu-Wc9MJA}9=jwCViQ9gy_-Pw^;+IMib8CrfTMT!_49qXu}h*5 tzH@}RJ_dKSmX{HQsPC@!XFjO3z5&>!z1-AA>j?k=002ovPDHLkV1kYnyn6ru literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/red/3.png b/src/main/resources/img/gem/red/3.png new file mode 100755 index 0000000000000000000000000000000000000000..145dd1249b204b8b1811a3dc1f5a8cb04b99a4ad GIT binary patch literal 406 zcmV;H0crk;P)Ij~B3(QxLs?g^7i=##m}?=LNWcr=YMf zdIV!bW9Uebx3Zhv$Nuvp1(QsOX5IPbv7$jRYG}%%Fvt&cxEA^I$o%?znmPcxg~Jtm z*@>=zctDw*AY8(20jwdc!!ZE&x)Fplyjy@m1V9H)^PyHP0Nf`t^Y}d{pei&x0J7-_ z@DG6fbSYhAvq=*(fOxeb5Co)b1=CQOPKZswWVD_GVEGVEtppGrP;^7|p7Qq90qT!q z0EnaY5288~vS1^CWD0zE`bgA2NCYBWSV33;pyt|eR|qM9Z*wdw2>51b0KCJgj1U+= z5)Ny!Bz6y{GC>Hp0o`{ngqIMe7`=253;?$;o2T!ae{(DqgaE)bhw#SDv1AbB9CX A%>V!Z literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/red/4.png b/src/main/resources/img/gem/red/4.png new file mode 100755 index 0000000000000000000000000000000000000000..6d522bed135db272a6758a416a073607164b35eb GIT binary patch literal 439 zcmV;o0Z9IdP)3?~-hp-ic-u38lF~`_NmMm3fPz?bM=Eef%y8na zknSi@TZiBo9k5OVuna&nm@pdvO_B@le|kdY@#UC5j)gHq3ZVxx6aZ>{@dqgfp*!IX zP{+Iw!qo`}Kq+%jDG07#M+<-g6Zhi#$=@90y&V8hG;qRP+;`d8g(08|6vEd2T9s2t zfT%w#grERHDyPaoaCAnH0SsC>m4eWRl^II3{{ZsbZ^BhhMYjV0jQM&dzpSn_^+X{W hH3w0MM$P{Utv9}VHG+4sEjR!G002ovPDHLkV1fs@s>%QW literal 0 HcmV?d00001 diff --git a/src/main/resources/img/gem/red/5.png b/src/main/resources/img/gem/red/5.png new file mode 100755 index 0000000000000000000000000000000000000000..bebd5a82f114b83a622ea0261fb1753f6c2ccf66 GIT binary patch literal 456 zcmV;(0XP1MP)0RU_Jg!SzGl z0ly=fZdg#dr~F7n0al%Ry9WfpccQEqhjPYN0E;=v>@Dy|bF;d+bs<<6R1i28$^2tc z@;v~EY4gq?fDa*=OG_)7lfall4^NQ)K zKu8T3^E&BW02D$55J-P*tPRlmA+$|5MgtG;n-p3r}1z|~F*&HQ7;G-y;-rF1{HX*e@(}yxgaR{*mOpni9 z!(8^8`sS!y6SM`E9zeicpK~o+O;!k+K6G*}3PDZH5gK?OhfmH05TElm1CiPJ