package de.miaurizius.jgame2d.entity; import de.miaurizius.jgame2d.core.Boot; import de.miaurizius.jgame2d.core.enums.Direction; import de.miaurizius.jgame2d.core.GamePanel; import de.miaurizius.jgame2d.core.Utility; import de.miaurizius.jgame2d.core.enums.EntityType; import de.miaurizius.jgame2d.core.enums.GameState; import de.miaurizius.jgame2d.entity.particle.Particle; import de.miaurizius.jgame2d.entity.projectile.Projectile; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.logging.Level; public class Entity { protected GamePanel panel; public BufferedImage up1, up2, down1, down2, left1, left2, right1, right2; public BufferedImage attackUp1, attackUp2, attackDown1, attackDown2, attackLeft1, attackLeft2, attackRight1, attackRight2; public BufferedImage guardUp, guardDown, guardLeft, guardRight; public BufferedImage image, image2, image3; public Rectangle solidArea = new Rectangle(0, 0, 48, 48); public Rectangle attackArea = new Rectangle(0, 0, 0, 0); public Entity attacker; public int solidAreaDefaultX, solidAreaDefaultY; public boolean collision; public String[][] dialogue = new String[20][20]; public Entity linkedEntity; // STATE public int worldX, worldY; public Direction direction = Direction.DOWN; public int spriteNum = 1; public int dialogueSet; public int dialogueIndex; public boolean collisionOn; public boolean invincible; public boolean transparent; public boolean attacking; public boolean alive = true; public boolean dying; public boolean hpBarOn; public boolean consumable; public boolean onPath; public boolean knockback; public boolean guarding; public boolean rage; public Direction knockbackDirection; // COUNTER public int spriteCount; public int actionLock; public int invincibleCount; public int shotAvailableCount; int dyingCount; int hpBarCount; int knockbackCount; // CHARACTER ATTRIBUTES public EntityType type; public int defaultSpeed; public String name; public int speed; public int maxLife; public int life; public int level; public int strength; public int dexterity; public int attack; public int defense; public int exp; public int nextLevelExp; public int coins; public int maxMana; public int mana; public Entity currentWeapon; public Entity currentShield; public Entity currentLight; public Projectile projectile; public List inventory = new ArrayList<>(); public final int maxInvSize = 20; // ITEM ATTRIBUTES public EntityType.WeaponType weaponType; public int attackValue; public int defenseValue; public String description; public int useCost; public int value; public int price; public int knockbackVal; public boolean stackable; public int amt = 1; public float lightRadius; public boolean opened; public Entity loot; public Entity(GamePanel panel) { this.panel = panel; } // DEFAULT public void update() { if(knockback) { checkCollision(); if(collisionOn) { knockbackCount = 0; knockback = false; speed = defaultSpeed; invincibleCounting(); return; } switch(knockbackDirection) { case UP -> worldY -= speed; case DOWN -> worldY += speed; case LEFT ->worldX -= speed; case RIGHT -> worldX += speed; } knockbackCount++; if(knockbackCount != 10) { invincibleCounting(); return; } knockback = false; knockbackCount = 0; speed = defaultSpeed; } else if(attacking) attacking(); else { setAction(); checkCollision(); if(!collisionOn) { switch (direction) { case UP -> worldY -= speed; case DOWN -> worldY += speed; case LEFT ->worldX -= speed; case RIGHT -> worldX += speed; } } spriteCount++; if(spriteCount > 24) { if(spriteNum == 1) spriteNum = 2; else if(spriteNum == 2) spriteNum = 1; else spriteNum = 0; spriteCount = 0; } } invincibleCounting(); } public void draw(Graphics2D graphics2d) { int screenX = worldX - panel.player.worldX + panel.player.screenX; int screenY = worldY - panel.player.worldY + panel.player.screenY; if(worldX + panel.tileSize*5 > panel.player.worldX - panel.player.screenX && worldX - panel.tileSize < panel.player.worldX + panel.player.screenX && worldY + panel.tileSize*5 > panel.player.worldY - panel.player.screenY && worldY - panel.tileSize < panel.player.worldY + panel.player.screenY ) { // MONSTER HP-BAR if(this.type == EntityType.MONSTER && hpBarOn) { graphics2d.setColor(new Color(35, 35, 35)); graphics2d.fillRect(screenX-1, screenY-6, panel.tileSize+2, 12); graphics2d.setColor(new Color(255, 0, 30)); graphics2d.fillRect(screenX, screenY-5, (int) ((double) panel.tileSize/maxLife)*life, 10); hpBarCount++; if(hpBarCount > 600) { //bar disappears after 10 seconds hpBarCount = 0; hpBarOn = false; } } // DRAW ENTITY if(invincible) { hpBarOn = true; hpBarCount = 0; if(transparent) changeOpacity(graphics2d, 0.4f); } if(dying) dyingAnimation(graphics2d); if(type == EntityType.PLAYER || name.equals("orc")) { // only modify sprite render position for player because I dont know yet how monster attack sprite are gonna look if(attacking) graphics2d.drawImage(parseSpriteATK(), (direction == Direction.LEFT) ? screenX - left1.getWidth() : screenX, (direction == Direction.UP) ? screenY - up1.getHeight() : screenY, null); else if(guarding) graphics2d.drawImage(parseSpriteGRD(), screenX, screenY, null); else graphics2d.drawImage(parseSprite(), screenX, screenY, null); } else graphics2d.drawImage(parseSprite(), screenX, screenY, null); changeOpacity(graphics2d, 1f); } if(panel.keyH.debug) { graphics2d.setColor(new Color(255, 0, 0, 70)); graphics2d.fillRect(worldX - panel.player.worldX + panel.player.screenX, worldY - panel.player.worldY + panel.player.screenY, panel.tileSize, panel.tileSize); } } // INTERACTION public void setAction() {} public void moveTowardPlayer(int interval) { actionLock++; if(actionLock > interval) { if(dX(panel.player) > dY(panel.player)) { if(panel.player.getCenterX() < getCenterX()) direction = Direction.LEFT; else direction = Direction.RIGHT; } else if(dX(panel.player) < dY(panel.player)) { if(panel.player.getCenterY() < getCenterY()) direction = Direction.UP; else direction = Direction.DOWN; } actionLock = 0; } } public void move(Direction direction) {} public void damageReaction() {} public void attacking() { if(panel.player.attackCancel && type == EntityType.PLAYER) return; spriteCount++; if(spriteCount <= 5) spriteNum = 1; if(spriteCount > 5 && spriteCount <= 25) { spriteNum = 2; int currentWorldX = worldX; int currentWorldY = worldY; int solidAreaWidth = solidArea.width; int solidAreaHeight = solidArea.height; switch(direction) { case UP -> worldY -= attackArea.height; case DOWN -> worldY += attackArea.height; case LEFT -> worldX -= attackArea.width; case RIGHT -> worldX += attackArea.width; } solidArea.width = attackArea.width; solidArea.height = attackArea.height; if(type == EntityType.MONSTER) if(panel.collisionH.checkPlayer(this)) damagePlayer(attack); if(type == EntityType.PLAYER) { int monsterIndex = panel.collisionH.checkEntity(this, panel.monster[panel.currentMap.getIndex()]); panel.player.damageMonster(monsterIndex, this, attack, currentWeapon.knockbackVal); int iTileIndex = panel.collisionH.checkEntity(this, panel.iTile[panel.currentMap.getIndex()]); panel.player.interactTile(iTileIndex); } worldX = currentWorldX; worldY = currentWorldY; solidArea.width = solidAreaWidth; solidArea.height = solidAreaHeight; } if(spriteCount > 25) { spriteNum = 1; spriteCount = 0; attacking = false; } } public void damagePlayer(int attack) { if(panel.player.invincible) return; boolean block = panel.player.guarding && panel.player.direction == this.direction.getOpposite(); int damage = attack - panel.player.defense; if(block) { panel.playSE(15); damage = 0; } else panel.playSE(6); panel.player.life -= Math.max(damage, (block ? 0 : 1)); if(damage != 0) { setKnockback(panel.player, this, knockbackVal); panel.player.transparent = true; } panel.player.invincible = true; } public void speak() { } public void facePlayer() { switch(panel.player.direction) { case UP -> direction = Direction.DOWN; case DOWN -> direction = Direction.UP; case LEFT -> direction = Direction.RIGHT; case RIGHT -> direction = Direction.LEFT; } } public void startDialogue(Entity entity, int setNum) { panel.gameState = GameState.DIALOGUE; panel.ui.tradingNPC = entity; dialogueSet = setNum; } public void dyingAnimation(Graphics2D graphics2d) { dyingCount++; int incr = 5; if(dyingCount <= incr) changeOpacity(graphics2d, 0f); if(dyingCount > incr && dyingCount <= incr*2) changeOpacity(graphics2d, 1f); if(dyingCount > incr*2 && dyingCount <= incr*3) changeOpacity(graphics2d, 0f); if(dyingCount > incr*3 && dyingCount <= incr*4) changeOpacity(graphics2d, 1f); if(dyingCount > incr*4 && dyingCount <= incr*5) changeOpacity(graphics2d, 0f); if(dyingCount > incr*5 && dyingCount <= incr*6) changeOpacity(graphics2d, 1f); if(dyingCount > incr*6 && dyingCount <= incr*7) changeOpacity(graphics2d, 0f); if(dyingCount > incr*7 && dyingCount <= incr*8) changeOpacity(graphics2d, 1f); if(dyingCount > incr*8) { alive = false; } } public boolean use(Entity entity) { return false; } //If entity is consumable public void checkDrop() { } public void checkCollision() { collisionOn = false; panel.collisionH.checkTile(this); panel.collisionH.checkObject(this, false); panel.collisionH.checkEntity(this, panel.npc[panel.currentMap.getIndex()]); panel.collisionH.checkEntity(this, panel.monster[panel.currentMap.getIndex()]); panel.collisionH.checkEntity(this, panel.iTile[panel.currentMap.getIndex()]); boolean contactPlayer = panel.collisionH.checkPlayer(this); if(this.type == EntityType.MONSTER && contactPlayer) damagePlayer(attack); } public void dropItem(Entity droppedItem) { for(int i = 0; i < panel.obj[panel.currentMap.getIndex()].length; i++) { if(panel.obj[panel.currentMap.getIndex()][i] == null) { panel.obj[panel.currentMap.getIndex()][i] = droppedItem; panel.obj[panel.currentMap.getIndex()][i].worldX = worldX; panel.obj[panel.currentMap.getIndex()][i].worldY = worldY; break; } } } public void interact() { } public int getDetected(Entity user, Entity[][] target, String targetName) { int index = 999; int nextWorldX = user.getLeftX(); int nextWorldY = user.getTopY(); switch(user.direction) { case UP -> nextWorldY = user.getTopY()-panel.player.speed; case DOWN -> nextWorldY = user.getBottomY()+panel.player.speed; case LEFT -> nextWorldX = user.getLeftX()-panel.player.speed; case RIGHT -> nextWorldX = user.getRightX()+panel.player.speed; } int col = nextWorldX / panel.tileSize; int row = nextWorldY / panel.tileSize; for(int i = 0; i < target[panel.currentMap.getIndex()].length; i++) { if(target[panel.currentMap.getIndex()][i] == null) continue; if( target[panel.currentMap.getIndex()][i].getCol() == col && target[panel.currentMap.getIndex()][i].getRow() == row && target[panel.currentMap.getIndex()][i].name.equalsIgnoreCase(targetName) ) { index = i; break; } } return index; } public void setKnockback(Entity target, Entity attacker, int knockbackVal) { this.attacker = attacker; target.knockbackDirection = attacker.direction; target.speed += knockbackVal; target.knockback = true; } public void setLoot(Entity loot) {} // PARTICLE SETUP public Color getParticleColor() { return null; } public int getParticleSize() { return -1; } public int getParticleSpeed() { return -1; } public int getParticleMaxLife() { return -1; } public void generateParticle(Entity generator, Entity target) { Color color = generator.getParticleColor(); int size = generator.getParticleSize(); int speed = generator.getParticleSpeed(); int maxLife = generator.getParticleMaxLife(); Particle p1 = new Particle(panel, target, color, size, speed, maxLife, -2, -1); Particle p2 = new Particle(panel, target, color, size, speed, maxLife, 2, -1); Particle p3 = new Particle(panel, target, color, size, speed, maxLife, -2, 1); Particle p4 = new Particle(panel, target, color, size, speed, maxLife, 2, 1); panel.particleList.add(p1); panel.particleList.add(p2); panel.particleList.add(p3); panel.particleList.add(p4); } // GETTERS public int getLeftX() { return worldX + solidArea.x; } public int getRightX() { return worldX + solidArea.x + solidArea.width; } public int getTopY() { return worldY + solidArea.y; } public int getBottomY() { return worldY + solidArea.y + solidArea.height; } public int getCol() { return (worldX + solidArea.x) / panel.tileSize; } public int getRow() { return (worldY + solidArea.y) / panel.tileSize; } public int dX(Entity target) { return Math.abs(getCenterX() - target.getCenterX()); } public int dY(Entity target) { return Math.abs(getCenterY() - target.getCenterY()); } public int getCenterX() { return worldX + left1.getWidth()/2; } public int getCenterY() { return worldY + up1.getHeight()/2; } public int dTile(Entity target) { //if(Objects.equals(name, "orc")) System.out.println("dX: " + dX(target) + " dY: " + dY(target)); return (dX(target) + dY(target)) / panel.tileSize; } public int getGoalCol(Entity target) { return (target.worldX+target.solidArea.x)/panel.tileSize; } public int getGoalRow(Entity target) { return (target.worldY+target.solidArea.y)/panel.tileSize; } // SETTING THINGS UP BufferedImage parseSprite() { return switch (direction) { case UP -> (spriteNum == 1) ? up1 : up2; case DOWN -> (spriteNum == 1) ? down1 : down2; case LEFT -> (spriteNum == 1) ? left1 : left2; case RIGHT -> (spriteNum == 1) ? right1 : right2; }; } BufferedImage parseSpriteATK() { return switch (direction) { case UP -> (spriteNum == 1) ? attackUp1 : attackUp2; case DOWN -> (spriteNum == 1) ? attackDown1 : attackDown2; case LEFT -> (spriteNum == 1) ? attackLeft1 : attackLeft2; case RIGHT -> (spriteNum == 1) ? attackRight1 : attackRight2; }; } BufferedImage parseSpriteGRD() { return switch (direction) { case UP -> guardUp; case DOWN -> guardDown; case LEFT -> guardLeft; case RIGHT -> guardRight; }; } public BufferedImage initEntitySprites(String name) { try { return Utility.scaleImage(ImageIO.read(new FileInputStream("assets/" + name + ".png")), panel.tileSize, panel.tileSize); } catch (IOException e) { Boot.logger.log(Level.SEVERE, "Could not load entity-image", e); } return null; } public BufferedImage initEntitySprites(String name, int width, int height) { try { return Utility.scaleImage(ImageIO.read(new FileInputStream("assets/" + name + ".png")), width, height); } catch (IOException e) { Boot.logger.log(Level.SEVERE, "Could not load entity-image", e); } return null; } public void setDialogue() {} public void changeOpacity(Graphics2D graphics2d, float opacity) { graphics2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity)); } public void searchPath(int goalCol, int goalRow) { int startCol = (worldX + solidArea.x) / panel.tileSize; int startRow = (worldY + solidArea.y) / panel.tileSize; panel.pFinder.setNodes(startCol, startRow, goalCol, goalRow); if (panel.pFinder.search()) { int nextCol = panel.pFinder.pathList.getFirst().col; int nextRow = panel.pFinder.pathList.getFirst().row; int enLeftX = worldX + solidArea.x; int enTopY = worldY + solidArea.y; int targetX = nextCol * panel.tileSize; int targetY = nextRow * panel.tileSize; // UP if (nextRow < startRow) { if (enLeftX < targetX) direction = Direction.RIGHT; else if (enLeftX > targetX) direction = Direction.LEFT; else direction = Direction.UP; } // DOWN else if (nextRow > startRow) { if (enLeftX < targetX) direction = Direction.RIGHT; else if (enLeftX > targetX) direction = Direction.LEFT; else direction = Direction.DOWN; } else if (nextCol < startCol) { // DOWN LEFT if (enTopY < targetY) direction = Direction.DOWN; else if (enTopY > targetY) direction = Direction.UP; else direction = Direction.LEFT; } else if (nextCol > startCol) { // RIGHT if (enTopY < targetY) direction = Direction.DOWN; else if (enTopY > targetY) direction = Direction.UP; else direction = Direction.RIGHT; } } else onPath = false; } public void followPlayer() { int goalCol = panel.player.getCol(); int goalRow = panel.player.getRow(); int startCol = (worldX + solidArea.x) / panel.tileSize; int startRow = (worldY + solidArea.y) / panel.tileSize; panel.pFinder.setNodes(startCol, startRow, goalCol, goalRow); if (panel.pFinder.search()) { int nextCol = panel.pFinder.pathList.getFirst().col; int nextRow = panel.pFinder.pathList.getFirst().row; int enLeftX = worldX + solidArea.x; int enTopY = worldY + solidArea.y; int targetX = nextCol * panel.tileSize; int targetY = nextRow * panel.tileSize; // UP if (nextRow < startRow) { if (enLeftX < targetX) direction = Direction.RIGHT; else if (enLeftX > targetX) direction = Direction.LEFT; else direction = Direction.UP; } // DOWN else if (nextRow > startRow) { if (enLeftX < targetX) direction = Direction.RIGHT; else if (enLeftX > targetX) direction = Direction.LEFT; else direction = Direction.DOWN; } else if (nextCol < startCol) { // DOWN LEFT if (enTopY < targetY) direction = Direction.DOWN; else if (enTopY > targetY) direction = Direction.UP; else direction = Direction.LEFT; } else if (nextCol > startCol) { // RIGHT if (enTopY < targetY) direction = Direction.DOWN; else if (enTopY > targetY) direction = Direction.UP; else direction = Direction.RIGHT; } } } public void checkStopChasing(Entity target, int distance, int rate) { //if(Objects.equals(name, "orc")) System.out.println("dTile: " + dTile(target) + " distance: " + distance); if(dTile(target) > distance) onPath = false; } public void checkStartChasing(Entity target, int distance, int rate) { if(dTile(target) < distance) onPath = true; } public void checkShooting(int rate, int shotInterval) { if(new Random().nextInt(rate) == 0 && projectile.alive == false && shotAvailableCount == shotInterval) { projectile.set(worldX, worldY, direction, true, this); // CHECK VACANCY for(int ii = 0; ii < panel.projectileList.size(); ii++) { if(panel.projectileList.get(ii) == null) { panel.projectileList.set(ii, projectile); break; } } shotAvailableCount = 0; } } public void checkAttack(int rate, int straight, int horizontal) { boolean targetInRange = false; int xDist = dX(panel.player); int yDist = dY(panel.player); switch(direction) { case UP -> { if(panel.player.getCenterY() < getCenterY() && yDist < straight && xDist < horizontal) targetInRange = true; } case DOWN -> { if(panel.player.getCenterY() > getCenterY() && yDist < straight && xDist < horizontal) targetInRange = true; } case LEFT -> { if(panel.player.getCenterX() < getCenterX() && xDist < straight && yDist < horizontal) targetInRange = true; } case RIGHT -> { if(panel.player.getCenterX() > getCenterX() && xDist < straight && yDist < horizontal) targetInRange = true; } } if(targetInRange) if (new Random().nextInt(rate) == 0) { attacking = true; spriteNum = 1; spriteCount = 0; shotAvailableCount = 0; } } public void setRandomDirection(int interval) { actionLock++; if(actionLock > interval) { //lock action for x frames Random rand = new Random(); int i = rand.nextInt(100)+1; //Generate number between 1 and 100 if(i <= 25) direction = Direction.UP; if(i > 25 && i <= 50) direction = Direction.DOWN; if(i > 50 && i <= 75) direction = Direction.LEFT; if(i > 75) direction = Direction.RIGHT; actionLock = 0; } } public void invincibleCounting() { if(!invincible) return; invincibleCount++; if(invincibleCount > 40) { invincible = false; invincibleCount = 0; } } public void resetCounter() { spriteCount = 0; actionLock = 0; invincibleCount = 0; shotAvailableCount = 0; dyingCount = 0; hpBarCount = 0; knockbackCount = 0; } }