import processing.core.*; 
import processing.data.*; 
import processing.event.*; 
import processing.opengl.*; 

import ddf.minim.*; 

import java.util.HashMap; 
import java.util.ArrayList; 
import java.io.File; 
import java.io.BufferedReader; 
import java.io.PrintWriter; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.io.IOException; 

public class Rite_of_the_Goddess extends PApplet {



Minim minim;
SE SE;
AudioPlayer BGMPlayer;
AudioPlayer BGSPlayer;

Input input;
Player player;
Level level;
Room room;
Fade fade;

String gameState;
boolean pause;
int currentRoom;

ArrayList <Enemy> enemies = new ArrayList <Enemy> ();
ArrayList <Candle> candles = new ArrayList <Candle> ();
ArrayList <DamageArea> damageAreas = new ArrayList <DamageArea> ();
ArrayList <Effect> effects = new ArrayList <Effect> ();
//ArrayList <OrderedObjects> enemies = new ArrayList <Object> ();

int animation;

PImage redIdle[] = new PImage [4];
PImage redWalk[] = new PImage [4];
PImage redThrow[] = new PImage [2];

PImage candleOn[] = new PImage [4];
PImage candlePower[] = new PImage [4];
PImage candleOff[] = new PImage [1];

PImage ghostIdle[] = new PImage [1];
PImage ghostDamage[] = new PImage [1];
PImage ghostChasing[] = new PImage [1];

PImage bigGhostIdle[] = new PImage [1];
PImage bigGhostDamage[] = new PImage [1];
PImage bigGhostChasing[] = new PImage [1];

PImage kingGhostIdle[] = new PImage [3];
PImage kingGhostDamage[] = new PImage [1];
PImage kingGhostChasing[] = new PImage [3];


PImage skullIdle[] = new PImage [4];
PImage skullDamage[] = new PImage [1];

PImage poof[] = new PImage [4];

PImage titleImg;
PImage introImg;
PImage gameOverImg;
PImage bgImg;
PImage tutorialImg;
PImage lastRoomImg;
PImage victoryImg;
PImage pauseImg;

PImage doorLocked;
PImage doorUnlocked;

PImage hudHeader;
PImage hudHeart;
PImage hudCandle;
PImage cursorImg;

PFont font;

public void setup (){
  
  //surface.setResizable(true);
  input = new Input();
  fade = new Fade();
  
  noCursor();
  //fullScreen();
  
  for (int i = 0; i<redIdle.length; i++) redIdle[i]= loadImage("Assets/Red/idle/red_iddle_"+i+".png");
  for (int i = 0; i<redWalk.length; i++) redWalk[i]= loadImage("Assets/Red/walk/red_walking_"+i+".png");
  for (int i = 0; i<redThrow.length; i++) redThrow[i]= loadImage("Assets/Red/throw/red_throw_"+i+".png");
  
  for (int i = 0; i<candleOn.length; i++) candleOn[i]= loadImage("Assets/Candle/vela_"+i+".png");
  for (int i = 0; i<candlePower.length; i++) candlePower[i]= loadImage("Assets/Candle/vela_power_"+i+".png");
  for (int i = 0; i<candleOff.length; i++) candleOff[i]= loadImage("Assets/Candle/vela_apagada_"+i+".png");
  
  for (int i = 0; i<ghostIdle.length; i++) ghostIdle[i]= loadImage("Assets/Ghost/ghost_idle_"+i+".png");
  for (int i = 0; i<ghostDamage.length; i++) ghostDamage[i]= loadImage("Assets/Ghost/ghost_hurt_"+i+".png");
  for (int i = 0; i<ghostChasing.length; i++) ghostChasing[i]= loadImage("Assets/Ghost/ghost_aggro_"+i+".png");
  
  for (int i = 0; i<bigGhostIdle.length; i++) bigGhostIdle[i]= loadImage("Assets/BigGhost/big_ghost_idle_"+i+".png");
  for (int i = 0; i<bigGhostDamage.length; i++) bigGhostDamage[i]= loadImage("Assets/BigGhost/big_ghost_hurt_"+i+".png");
  for (int i = 0; i<bigGhostChasing.length; i++) bigGhostChasing[i]= loadImage("Assets/BigGhost/big_ghost_chasing_"+i+".png");
  
  for (int i = 0; i<kingGhostIdle.length; i++) kingGhostIdle[i]= loadImage("Assets/King/king_idle_"+i+".png");
  for (int i = 0; i<kingGhostDamage.length; i++) kingGhostDamage[i]= loadImage("Assets/King/king_hurt_"+i+".png");
  for (int i = 0; i<kingGhostChasing.length; i++) kingGhostChasing[i]= loadImage("Assets/King/king_agro_"+i+".png");
  
  for (int i = 0; i<skullIdle.length; i++) skullIdle[i]= loadImage("Assets/FlamingSkull/skull_idle_"+i+".png");
  for (int i = 0; i<skullDamage.length; i++) skullDamage[i]= loadImage("Assets/FlamingSkull/skull_damage_"+i+".png");
  
  for (int i = 0; i<poof.length; i++) poof[i]= loadImage("Assets/Poof/poof_"+i+".png");
  
  titleImg = loadImage("Assets/title_screen.png");
  introImg = loadImage("Assets/intro_screen.png");
  gameOverImg = loadImage("Assets/game_over_screen.png");
  bgImg = loadImage("Assets/BG.png");
  tutorialImg = loadImage("Assets/tutorial_screen.png");
  lastRoomImg = loadImage("Assets/last_floor.png");
  victoryImg = loadImage("Assets/victory_screen.png");
  pauseImg = loadImage("Assets/paused_screen.png");
  
  hudHeader = loadImage("Assets/Red/header.png");
  hudHeart = loadImage("Assets/hud_heart.png");
  hudCandle = loadImage("Assets/hud_candle.png");
  cursorImg = loadImage("Assets/cursor.png");
  
  doorLocked = loadImage("Assets/door_locked.png");
  doorUnlocked = loadImage("Assets/door_unlocked.png");
  
  font = loadFont("SFAtarianSystem-28.vlw");
  textFont(font);
   
  SE = new SE();
  minim = new Minim (this);
  SE.initAllSounds();
  BGMPlayer = minim.loadFile("Assets/sound/mus_menu.wav");
  BGSPlayer = minim.loadFile("Assets/sound/mus_muted.wav");
  //BGMPlayer.loop();
  
  gameState = "title";
  setBGM("mus_menu");
  
}

public void draw(){
  input.check();
  switch(gameState){
    case "title":
      titleRender();
      titleUpdate();
      break;
    case "intro":
      introRender();
      introUpdate();
      break;
    case "game":
        gameRender();
        if (!pause && !fade.fading){
          gameUpdate();
        }
        else if (pause){
          pauseRender();
          pauseUpdate();
        }
      break;
    case "gameOver":
      gameOverRender();
      gameOverUpdate();
      break;
    case "endGame":
      endGameRender();
      endGameUpdate();
      break;
  }
  if (!gameState.equals("game")) fade.render();
  fade.update();
        
  input.end();
}

public void titleRender(){
  image(titleImg,0,0);
}
public void titleUpdate(){
  if (!fade.fading){
    if(input.mouseLeft.justPressed){
      fade.start(120, color(255), "intro");
      SE.trigger(SE.menuConfirm);
    }
    else if (input.Q.justPressed){
      exit();
    }
  }
}

public void introRender(){
  image(introImg,0,0);
}
public void introUpdate(){
  if (!fade.fading && input.mouseLeft.justPressed){
    fade.start(120, color(255), "startGame");
  }
}

public void gameRender(){
  
  //pushMatrix();
  //translate(-player.x, -player.y);
  level.render();
  //popMatrix();
  
  for (DamageArea d : damageAreas) d.render();  
  player.render();
  for (Candle c : candles) c.render();
  for (Effect f : effects) f.render();
  for (Enemy e : enemies) e.render();
  fade.render();
  renderHUD();
}

public void gameUpdate(){
  animation++;
  if (animation > 200000) animation = 0;
  fade.update();  
  
  level.update();
  player.update();
  
  ArrayList <Candle> candlesToActivate = new ArrayList <Candle> ();
  ArrayList <Candle> candlesToRemove = new ArrayList <Candle> ();
  
  Point [] points = new Point [3];
  
  for (Candle c : candles) {
    c.update();
    if (candlesToActivate.size() < 3 && !c.flying && !c.activated) {
      candlesToActivate.add(c);
    }
    if (candlesToActivate.size() == 3) {
      int i = 0;
      for (Candle ca : candlesToActivate) {
        ca.activate();
        points[i] = new Point (ca.x, ca.y);
        i++;
      }
      SE.trigger(SE.ignite);
      DamageArea d = new DamageArea(points);
      damageAreas.add(d);
      candlesToActivate.clear();
    }
    if (!c.lit && player.hitBox.overlap(c.hitBox)){
      SE.trigger(SE.collect);
      candlesToRemove.add(c);
    }
  }
  for (Candle c : candlesToRemove) {
    candles.remove(c);
  }
  
  ArrayList <DamageArea> areasToRemove = new ArrayList <DamageArea> ();
  for (DamageArea d : damageAreas) {
    d.update();
    if(d.timer<0) areasToRemove.add(d);
  }
  for (DamageArea d : areasToRemove) {
    damageAreas.remove(d);
  }
  
  ArrayList <Enemy> enemiesToRemove = new ArrayList <Enemy> ();
  for (Enemy e : enemies) {
    if (e.flinch <= 0) {
      if (player.flinch <= 0 && player.hitBox.overlap(e.hitBox)) {
        player.takeDamage(e.power);
      }
      for (DamageArea d : damageAreas) {
        if (isInsideTriangle(e.x, e.y, d.points[0].x, d.points[0].y, d.points[1].x, d.points[1].y, d.points[2].x, d.points[2].y)){
          e.takeDamage(d.power);
        }
      }
    }
    e.update();
    if (!e.active) {
      enemiesToRemove.add(e);
      effects.add(new Effect(e.x, e.y));
    }
  }
  for (Enemy e : enemiesToRemove) {
    enemies.remove(e);
  }
  
  ArrayList <Effect> effectsToRemove = new ArrayList <Effect> ();
  for (Effect f : effects) {
    f.update();
    if (!f.active){
      effectsToRemove.add(f);
    }
  }
  for (Effect f : effectsToRemove) {
    effects.remove(f);
  }
  
  
  if (input.start.justPressed || input.esc.justPressed){
    pause = true;
  }
}

public void pauseRender(){
  image(pauseImg,0,0);
}
public void pauseUpdate(){
  if (input.start.justPressed || input.esc.justPressed){
    pause = false;
  }
  if (input.Q.justPressed){
    gameState = "title";
    pause = false;
  }
}


public void gameOverRender(){
  image(gameOverImg,0,0);
}
public void gameOverUpdate(){
  if (!fade.fading && input.mouseLeft.justPressed){
    fade.start(60, color(255), "startGame");
  }
  if (!fade.fading && input.Q.justPressed){
    gameState = "title";
  }
}

public void endGameRender(){
  image(victoryImg,0,0);
}
public void endGameUpdate(){
  if (!fade.fading && input.mouseLeft.justPressed){
    gameState = "title";
  }
}


public void startGame(){
  gameState = "game";
  setBGM("mus_gameplay");
  level = new Level();
  player = new Player();

  currentRoom = 0;
  level.loadRoom(currentRoom);
}

public void clearRoom(){
  candles.clear();
  enemies.clear();
  damageAreas.clear();  
}

public void imageX(PImage I, float X, float Y, boolean b) {
  if (!b){
    pushMatrix();
    translate(X + I.width, 0);
    scale(-1, 1);
    image (I, 0, Y);
    popMatrix();
  }
  else{
    image (I, X, Y);
  }
}

public void imageY(PImage I, float X, float Y) {
  pushMatrix();
  translate(0, Y + I.height);
  scale(1, -1);
  image (I, X, 0);
  popMatrix();
}
class Effect extends Object {
  int timer;
  boolean active;
  Effect(float x, float y){
    this.x = x;
    this.y = y;
    timer = 0;
    active = true;
    println(x);
    println(y);
  }
  public void update(){
    timer++;
    if (PApplet.parseInt(timer/(2)) >= poof.length) {
      active = false;
    }
  }
  public void render(){
    if(active){
      imageMode(CENTER);
      image(poof[PApplet.parseInt(timer/(2))], x, y);
      imageMode(CORNER);
    }
  }
}
class Candle extends Object{
  float aimX, aimY;
  float z;
  float speed;
  float angle;
  boolean flying, lit, activated;
  int flyTime;
  int timer;
  
  Candle(float x, float y, float aimX, float aimY){
    this.x = x;
    this.y = y;
    this.aimX = aimX;
    this.aimY = aimY;
    z = 0;
    float distance = dist(x, y, aimX, aimY);
    flyTime = 0;
    this.speed = distance / candleAirTime;
    this.angle = atan2(aimY - y, aimX - x);
    flying = true;
    lit = true;
    
    spriteOffsetX = -candleOn[0].width/2 + 1;
    spriteOffsetY = -44;
    
    hitBox = new HitBox (-6, -4, 12, 8);
  }
    
  public void render(){
    
    fill(0);
    noStroke();
    ellipseMode(CENTER);
    ellipse(x,y,10,6);
    
    if(!lit){
      tint(200);
      image(candleOff[0],x + spriteOffsetX, y + spriteOffsetY - z);
      noTint();
    }
    else if (activated) image(candlePower[ animation % 4 ],x + spriteOffsetX, y + spriteOffsetY - z);
    else image(candleOn[ animation/10 % 4 ],x + spriteOffsetX, y + spriteOffsetY - z);
  }
  
  public void update(){
    
    if (flying){
      x+=cos(angle)*speed;
      y+=sin(angle)*speed;
      
      if (x > level.rightBorder) {
        x = level.rightBorder;
      }
      if (x < level.leftBorder) {
        x = level.leftBorder;
      }
      if (y > level.bottomBorder) {
        y = level.bottomBorder;
      }
      if (y < level.topBorder) {
        y = level.topBorder;
      }
      
      flyTime++;
      
      float a = -4 * candleMaxHeight / pow(candleAirTime, 2);
      float b = 4 * candleMaxHeight / candleAirTime;
      z = a * pow (flyTime, 2) + b * flyTime;
      
      if (z < 0) {
        z = 0;
        flying = false;
      }
    }
    
    if (activated) {
      timer--;
      if (timer == 0) lit = false;
    }
    
    super.update();
  }
  
  public void activate(){
    activated = true;
    timer = damageAreaTime;
  }
}
int candleMax = 6;

int maxDist = 300;
int candleAirTime = 30;
int candleMaxHeight = 100;

int damageAreaTime = 60;

int enemyFlinchTime = 2*60;
int playerFlinchTime = 2*60;

float minDamage = 20;
float maxDamage = 60;
float minDamageArea = 8000;
float maxDamageArea = 0; // definido no level

int noticing_distance = 150;
class DamageArea {
 Point [] points;
 int timer;
 float power;
 
 DamageArea(Point [] points){
   this.points = points;
   timer = damageAreaTime;
   float area = max (triangleArea(points), minDamageArea);
   //power = area/
   //println(power);
   power = 1;
 }
  
 public void render() {
   //if (timer % 2 == 0) {
     //float intensity = 1 - power / maxDamageArea;
     //fill(211,32,32, 255 * intensity);
     fill(211, 32, 32, 255 * (PApplet.parseFloat(timer) / damageAreaTime));
     beginShape();
     for (int i = 0; i < points.length; i++) {
       vertex(points[i].x, points[i].y);
     }
     endShape(CLOSE);
   //}
 }
 
 public void update() {
   timer--;
 }
 
}

class Point {
  float x, y;
  Point (float x, float y){
    this.x = x;
    this.y = y;
  }
}

public float triangleArea(Point [] points){
  float a;
  a = abs (points[0].x * (points[1].y - points[2].y) + points[1].x * (points[2].y - points[0].y) + points[2].x * (points[0]. y - points[1].y)) / 2;
  return a;
}

public boolean isInsideTriangle(float x, float y, float x1, float y1, float x2, float y2, float x3, float y3){
 Point p1 = new Point(x1, y1);
 Point p2 = new Point(x2, y2);
 Point p3 = new Point(x3, y3);
  
 Point p = new Point(x,y); // <-- You are checking if this point lies in the triangle.
 //Now, the barycentric coordinates, generally called alpha, beta, and gamma, are calculated as follows:
  
 float alpha = ((p2.y - p3.y)*(p.x - p3.x) + (p3.x - p2.x)*(p.y - p3.y)) /
  ((p2.y - p3.y)*(p1.x - p3.x) + (p3.x - p2.x)*(p1.y - p3.y));
 float beta = ((p3.y - p1.y)*(p.x - p3.x) + (p1.x - p3.x)*(p.y - p3.y)) /
  ((p2.y - p3.y)*(p1.x - p3.x) + (p3.x - p2.x)*(p1.y - p3.y));
 float gamma = 1.0f - alpha - beta;
 //If all of alpha, beta, and gamma are greater than 0, then the point p lies within the triangle made of points p1, p2, and p3.  
 //The explanation behind this is that a point inside a triangle can be described using the points of the triangle, and three coefficients (one for each point, in the range [0,1]):
  
 //p = (alpha)*p1 + (beta)*p2 + (gamma)*p3;
 
 boolean t = (alpha > 0 && beta > 0 && gamma > 0);
 //println(t);
 return t;
}
class Enemy extends Object {
  float hp, maxHp;
  int flinch;
  String state;
  boolean active;
  int power;
  
  Enemy (int x, int y){
    this.x = x;
    this.y = y;
    active = true;
    flinch = 0;
    power = 1;
  }
  public void update(){
    super.update();
  }
  public void renderHealthBar(){
    fill(0);
    rect (0,0,100,10);
    fill (255,0,0);
    rect (0,0,100 * hp/maxHp,10);
  }
  public void render(){
    if (hp < maxHp) renderHealthBar();
  }
  public void takeDamage(float d){
    SE.trigger(SE.damageEnemy);
    hp -=d;
    if (hp < 0) hp = 0;
  }
}


//====================== GHOST =========================


class Ghost extends Enemy {
  int ignoringTime = 150;
  int actionTimer;
  int noticingCooldown;
  
  int facingDirection = 1;
  int takingDamage;
  float speed1, speed2, angle;
  
  float z, vz;
  
  Ghost(int x, int y){
    super(x,y);
    hp = 1;
    state = "idle";
    speed1 = 2.5f;
    speed2 = 2.5f;
    power = 1;
    
    spriteOffsetX = -ghostIdle[0].width/2;
    spriteOffsetY = -70;
    
    hitBox = new HitBox (-22, -15, 44, 30);
  }
  
  public void update(){
    facingDirection = (cos(angle) > 0 ? 1 : -1); //resolver
    
    if (takingDamage > 0) {
      x -= facingDirection * 5;
      takingDamage --;
      if (takingDamage == 0 && hp <= 0){
        active = false;
      }
    }
    else switch (state){
      case "idle":
        actionTimer--;
        noticingCooldown--;
        
        if(noticingCooldown < 0 && dist(x,y,player.x,player.y) < noticing_distance){
          state = "jumping";
          vz = -6;
        }
        else if (actionTimer <=0) {
          state = "walking";
          actionTimer = PApplet.parseInt(random(100,200));
          angle = atan2(level.getRandomX(20) - y, level.getRandomY(20) - x);
          //angle = random(0, TWO_PI);
        }
        break;
      case "walking":
        actionTimer--;
        noticingCooldown--;
        
        x+=cos(angle)*speed1;
        y+=sin(angle)*speed1;
        
        if(noticingCooldown < 0 && dist(x,y,player.x,player.y) < noticing_distance){
          state = "jumping";
          vz = -6;
        }
        else if (actionTimer <=0 || collided) {
          state = "idle";
          actionTimer = PApplet.parseInt(random(100,200));
        }
        break;
      case "jumping":
        z += vz;
        vz += 0.6f;
        if(z > 0){
          z = 0;
          state = "chasing";
          actionTimer = 100;
        }
        break; 
      case "chasing":
        angle = atan2(player.y - y, player.x - x);
        x+=cos(angle)*speed2;
        y+=sin(angle)*speed2;
        actionTimer--;
        if (actionTimer < 0 && dist(x,y,player.x,player.y) > 100){
          state = "idle";
          actionTimer = PApplet.parseInt(random(100,200));
          noticingCooldown = ignoringTime;
        }
        break;
    }
    if (flinch > 0){
      flinch--;
    }
    super.update();
  }
  
  public void takeDamage(float d){
    super.takeDamage(d);
    takingDamage = 10;
    flinch = enemyFlinchTime;
  }
  
  public void render(){
    fill(0);
    noStroke();
    ellipseMode(CENTER);
    ellipse(x,y,20,12);
    
    if (flinch <= 0 || takingDamage > 0 || animation % 2 == 0){
      float jumpAnim = 8 * sin (animation * 0.05f);
      if (state.equals("jumping")){
        jumpAnim = z;
      }
      pushMatrix();
      translate(0, jumpAnim);
      
      if (takingDamage > 0) imageX(ghostDamage[0], x + spriteOffsetX, y + spriteOffsetY, (facingDirection < 0));
      else if (state.equals("chasing")) imageX(ghostChasing[0], x + spriteOffsetX, y + spriteOffsetY, (facingDirection < 0));
      else imageX(ghostIdle[0], x + spriteOffsetX, y + spriteOffsetY, (facingDirection < 0));
      popMatrix();
    }
    //hitBox.render();
    super.render();
  }
}

//====================== SKULL =========================


class Skull extends Enemy {
  int facingDirection = 1;
  int takingDamage;
  float speed, angle;
  
  Skull(int x, int y){
    super(x,y);
    hp = 1;
    state = "idle";
    speed = 3;
    speedX = speed;
    speedY = speed;
    power = 1;
    
    spriteOffsetX = -skullIdle[0].width/2;
    spriteOffsetY = -120;
    
    hitBox = new HitBox (-30, -15, 60, 30);
  }
  
  public void update(){
    facingDirection = (speedX > 0 ? 1 : -1); //resolver
    
    if (takingDamage > 0) {
      x -= facingDirection * 5;
      takingDamage --;
      if (takingDamage == 0 && hp <= 0){
        active = false;
      }
    }
    else {
      x += speedX;
      y += speedY;
    }
    if (flinch > 0){
      flinch--;
    }
    super.update();
  }
  
  public void takeDamage(float d){
    super.takeDamage(d);
    takingDamage = 10;
    flinch = enemyFlinchTime;
  }
  
  public void render(){
    fill(0);
    noStroke();
    ellipseMode(CENTER);
    ellipse(x,y,20,12);
    
    if (flinch <= 0 || takingDamage > 0 || animation % 2 == 0){
      float jumpAnim = 8 * sin (animation * 0.05f);
      pushMatrix();
      translate(0, jumpAnim);
      
      if (takingDamage > 0) imageX(skullDamage[0], x + spriteOffsetX, y + spriteOffsetY, (facingDirection < 0));
      else imageX(skullIdle[animation/6 % 4], x + spriteOffsetX, y + spriteOffsetY, (facingDirection < 0));
      popMatrix();
    }
    //hitBox.render();
    super.render();
  }
}

//====================== BIG GHOST =========================


class BigGhost extends Enemy {
  int ignoringTime = 75;
  int actionTimer;
  int noticingCooldown;
  
  int facingDirection = 1;
  int takingDamage;
  float speed1, speed2, angle;
  
  float z, vz;
  
  BigGhost(int x, int y){
    super(x,y);
    hp = 2;
    state = "idle";
    speed1 = 2.5f;
    speed2 = 2.5f;
    power = 1;
    
    spriteOffsetX = -bigGhostIdle[0].width/2;
    spriteOffsetY = -90;
    
    hitBox = new HitBox (-22, -15, 44, 30);
  }
  
  public void update(){
    facingDirection = (cos(angle) > 0 ? 1 : -1); //resolver
    
    if (takingDamage > 0) {
      x -= facingDirection * 5;
      takingDamage --;
      if (takingDamage == 0 && hp <= 0){
        active = false;
      }
    }
    else switch (state){
      case "idle":
        actionTimer--;
        noticingCooldown--;
        
        if(noticingCooldown < 0 && dist(x,y,player.x,player.y) < noticing_distance){
          state = "jumping";
          vz = -6;
        }
        else if (actionTimer <=0) {
          state = "walking";
          actionTimer = PApplet.parseInt(random(100,200));
          angle = atan2(level.getRandomX(20) - y, level.getRandomY(20) - x);
          //angle = random(0, TWO_PI);
        }
        break;
      case "walking":
        actionTimer--;
        noticingCooldown--;
        
        x+=cos(angle)*speed1;
        y+=sin(angle)*speed1;
        
        if(noticingCooldown < 0 && dist(x,y,player.x,player.y) < noticing_distance){
          state = "jumping";
          vz = -6;
        }
        else if (actionTimer <=0 || collided) {
          state = "idle";
          actionTimer = PApplet.parseInt(random(100,200));
        }
        break;
      case "jumping":
        z += vz;
        vz += 0.6f;
        if(z > 0){
          z = 0;
          state = "chasing";
          actionTimer = 100;
        }
        break; 
      case "chasing":
        angle = atan2(player.y - y, player.x - x);
        x+=cos(angle)*speed2;
        y+=sin(angle)*speed2;
        actionTimer--;
        if (actionTimer < 0 && dist(x,y,player.x,player.y) > 100){
          state = "idle";
          actionTimer = PApplet.parseInt(random(100,200));
          noticingCooldown = ignoringTime;
        }
        break;
    }
    if (flinch > 0){
      flinch--;
    }
    super.update();
  }
  
  public void takeDamage(float d){
    super.takeDamage(d);
    takingDamage = 10;
    flinch = enemyFlinchTime;
  }
  
  public void render(){
    fill(0);
    noStroke();
    ellipseMode(CENTER);
    ellipse(x,y,40,12); //elipse da sombra::????
    
    if (flinch <= 0 || takingDamage > 0 || animation % 2 == 0){
      float jumpAnim = 8 * sin (animation * 0.05f);
      if (state.equals("jumping")){
        jumpAnim = z;
      }
      pushMatrix();
      translate(0, jumpAnim);
      
      if (takingDamage > 0) imageX(bigGhostDamage[0], x + spriteOffsetX, y + spriteOffsetY, (facingDirection < 0));
      else if (state.equals("chasing")) imageX(bigGhostChasing[0], x + spriteOffsetX, y + spriteOffsetY, (facingDirection < 0));
      else imageX(bigGhostIdle[0], x + spriteOffsetX, y + spriteOffsetY, (facingDirection < 0));
      popMatrix();
    }
    //hitBox.render();
    super.render();
  }
}

//====================== BIG GHOST =========================


class KingGhost extends Enemy {
  int ignoringTime = 75;
  int actionTimer;
  int noticingCooldown;
  
  int facingDirection = 1;
  int takingDamage;
  float speed1, speed2, angle;
  
  float z, vz;
  
  KingGhost(int x, int y){
    super(x,y);
    hp = 3;
    state = "idle";
    speed1 = 2.7f;
    speed2 = 2.7f;
    power = 1;
    
    spriteOffsetX = -kingGhostIdle[0].width/2;
    spriteOffsetY = -130;
    
    hitBox = new HitBox (-30, -15, 60, 30);
  }
  
  public void update(){
    facingDirection = (cos(angle) > 0 ? 1 : -1); //resolver
    
    if (takingDamage > 0) {
      x -= facingDirection * 5;
      takingDamage --;
      if (takingDamage == 0 && hp <= 0){
        active = false;
      }
    }
    else switch (state){
      case "idle":
        actionTimer--;
        noticingCooldown--;
        
        if(noticingCooldown < 0 && dist(x,y,player.x,player.y) < noticing_distance){
          state = "jumping";
          vz = -6;
        }
        else if (actionTimer <=0) {
          state = "walking";
          actionTimer = PApplet.parseInt(random(100,200));
          angle = atan2(level.getRandomX(20) - y, level.getRandomY(20) - x);
          //angle = random(0, TWO_PI);
        }
        break;
      case "walking":
        actionTimer--;
        noticingCooldown--;
        
        x+=cos(angle)*speed1;
        y+=sin(angle)*speed1;
        
        if(noticingCooldown < 0 && dist(x,y,player.x,player.y) < noticing_distance){
          state = "jumping";
          vz = -6;
        }
        else if (actionTimer <=0 || collided) {
          state = "idle";
          actionTimer = PApplet.parseInt(random(100,200));
        }
        break;
      case "jumping":
        z += vz;
        vz += 0.6f;
        if(z > 0){
          z = 0;
          state = "chasing";
          actionTimer = 160;
        }
        break; 
      case "chasing":
        angle = atan2(player.y - y, player.x - x);
        x+=cos(angle)*speed2;
        y+=sin(angle)*speed2;
        actionTimer--;
        if (actionTimer < 0 && dist(x,y,player.x,player.y) > 100){
          state = "idle";
          actionTimer = PApplet.parseInt(random(100,200));
          noticingCooldown = ignoringTime;
        }
        break;
    }
    if (flinch > 0){
      flinch--;
    }
    super.update();
  }
  
  public void takeDamage(float d){
    super.takeDamage(d);
    takingDamage = 10;
    flinch = enemyFlinchTime;
  }
  
  public void render(){
    fill(0);
    noStroke();
    ellipseMode(CENTER);
    ellipse(x,y,60,20); //elipse da sombra::????
    
    if (flinch <= 0 || takingDamage > 0 || animation % 2 == 0){
      float jumpAnim = 8 * sin (animation * 0.05f);
      if (state.equals("jumping")){
        jumpAnim = z;
      }
      pushMatrix();
      translate(0, jumpAnim);
      
      if (takingDamage > 0) imageX(kingGhostDamage[0], x + spriteOffsetX, y + spriteOffsetY, (facingDirection < 0));
      else if (state.equals("chasing")) imageX(kingGhostChasing[animation/6 % 3], x + spriteOffsetX, y + spriteOffsetY, (facingDirection < 0));
      else imageX(kingGhostIdle[animation/6 % 3], x + spriteOffsetX, y + spriteOffsetY, (facingDirection < 0));
      popMatrix();
    }
    //hitBox.render();
    super.render();
  }
}
class Fade {
  float value;
  int c;
  float increment;

  boolean fading;
  boolean out;
  
  String effect;
  
  Fade(){
    value = 0;
    fading = false;
  }
  public void start(float time, int c, String effect){
    increment = 1 / time;
    this.c = c;
    this.effect = effect;
    fading = true;
    out = true;
    
    value = 0;
  }
  public void render(){
    fill(c, 255 * value);
    noStroke();
    rect(0,0,width,height);
  }
  public void update(){
    if (fading){
      if (out){
        value += increment;
        if (value >=1){
          value = 1;
          checkEffect(effect);
          out = false;
        }
      }
      else{
        value -= increment;
        if (value <=0){
          value = 0;
          fading = false;
        }
      }
    }
  }
}

public void checkEffect(String e){
  switch (e){
    case "intro":
      gameState = "intro";
      setBGM("mus_menu");
      break;
    case "startGame":
      startGame();
      break;
    case "nextRoom":
      level.nextRoom();
      break;
    case "gameOver":
      gameState = "gameOver";
      setBGM("mus_menu");
      break;
    case "endGame":
      gameState = "endGame";
      setBGM("mus_menu");
      break;
  }
}

class HitBox {
  float x, y, offsetX, offsetY, width, height;
  
  HitBox(float offsetX, float offsetY, float w, float h){
    this.offsetX = offsetX;
    this.offsetY = offsetY;
    width = w;
    height = h;    
  }
  
  public void update(float x, float y){
    this.x = x + offsetX;
    this.y = y + offsetY;
  }
  
  public void render() {
    stroke(0,255,0);
    noFill();
    rectMode(CORNER);
    rect (x , y , width, height);
    noStroke();
  }
  public boolean overlap(HitBox b){
    return (x + width > b.x && b.x + b.width > x && y + height > b.y && b.y + b.height > y);
  }
}
class Key {
  boolean isDown = false;
  boolean pstate = false;
  boolean justPressed=false;
  boolean justReleased=false;
  public void reset() {
    justPressed=false;
    justReleased=false;
  }
  public void end() {
    pstate=isDown;
  }
}

class MouseButton {
  boolean isDown = false;
  boolean pState = false;
  boolean justPressed = false;
}

class Input {
  Key A, B, Q, start, esc;
  Key left, right, up, down;
  MouseButton mouseLeft; //fazer algum dia com Key
  
  Input() {
    A= new Key();
    B= new Key();
    Q= new Key();
    left= new Key();
    right= new Key();
    up= new Key();
    down= new Key();
    start= new Key();
    esc= new Key();
    mouseLeft = new MouseButton();
  }
  
  public void check(){
    if (mousePressed) {
      mouseLeft.isDown = true;
      if (!mouseLeft.pState) {
        mouseLeft.justPressed = true;
      }
      else {
        mouseLeft.justPressed = false;
      }
    }
    else {
      mouseLeft.isDown = false;
    }
    mouseLeft.pState = mouseLeft.isDown;
  }
  
  public void end() {
    A.reset();
    B.reset();
    Q.reset();
    start.reset();
    up.reset();
    down.reset();
    left.reset();
    right.reset();
    esc.reset();
  }
}

public void keyPressed() { //interceptar o ESC
  if (key == ENTER || key == ' ') {
    if (input.start.isDown==false) input.start.justPressed=true;
    input.start.isDown=true;
  }
  if (key == 'z' || key == 'Z') {
    if (input.A.isDown==false) input.A.justPressed=true;
    input.A.isDown=true;
  }
  if (key == 'q' || key == 'Q') {
    if (input.Q.isDown==false) input.Q.justPressed=true;
    input.Q.isDown=true;
  }
  
  if (key == 'w' || key == 'W') {
    if (input.up.isDown==false) input.up.justPressed=true;
    input.up.isDown=true;
  }
  if (key == 'a' || key == 'A') {
    if (input.left.isDown==false) input.left.justPressed=true;
    input.left.isDown=true;
  }
  if (key == 's' || key == 'S') {
    if (input.down.isDown==false) input.down.justPressed=true;
    input.down.isDown=true;
  }
  if (key == 'd' || key == 'D') {
    if (input.right.isDown==false) input.right.justPressed=true;
    input.right.isDown=true;
  }
  
  if (key == 'm') {
    //println("hero position: "+hero.x+", "+hero.y);
  }
  if (key==27) {
    key=0;
    input.esc.justPressed=true;
  }
}

public void keyReleased() {
  if (key == ENTER || key == ' ') {
    input.start.justReleased=true;
    input.start.isDown=false;
  }
  if (key == 'z' || key == 'Z') {
    input.A.justReleased=true;
    input.A.isDown=false;
  }
  if (key == 'q' || key == 'Q') {
    input.Q.justReleased=true;
    input.Q.isDown=false;
  }
  
  if (key == 'w' || key == 'W') {
    input.up.justReleased=true;
    input.up.isDown=false;
  }
  if (key == 'a' || key == 'A') {
    input.left.justReleased=true;
    input.left.isDown=false;
  }
  if (key == 's' || key == 'S') {
    input.down.justReleased=true;
    input.down.isDown=false;
  }
  if (key == 'd' || key == 'D') {
    input.right.justReleased=true;
    input.right.isDown=false;
  }
}
class Level {
  int wallSize = 72;
  float levelArea;
  int leftBorder, rightBorder, topBorder, bottomBorder;
  
  Level(){
    leftBorder = wallSize;
    topBorder = wallSize;
    rightBorder = width - wallSize;
    bottomBorder = height - wallSize;
    levelArea = (rightBorder - leftBorder) * (bottomBorder - topBorder);
    maxDamageArea = levelArea / 2;
  }
  
  public float getRandomX(int offset){
    return(random(leftBorder + offset,rightBorder - offset));
  }
  public float getRandomY(int offset){
    return(random(topBorder + offset, bottomBorder - offset));
  }
  
  public void update() {
    room.update();
  }
  
  public void render(){
    room.render();
  }
  
  public void nextRoom(){
    currentRoom++;
    loadRoom(currentRoom);
    player.x = width/2;
    player.y = level.bottomBorder;
    player.update();
  }
  
  public void loadRoom(int r){
    clearRoom();  
    switch (r){
      case 0:
        room = new TutorialRoom();
        break;
      case 1:
        room = new BattleRoom();
        enemies.add(new Ghost(600,300));
        
        break;
      case 2:
        room = new MonologueRoom("I am so tired. I wish I knew what i was doing... I miss my mother, strange as that may be.");
        break;
      case 3:
        room = new BattleRoom();
        enemies.add(new Ghost(300,300));
        enemies.add(new Ghost(600,300));
        break;
      case 4:
        room = new BattleRoom();
        //enemies.add(new Ghost(600,300));
        enemies.add(new Skull(100,300));
        break;
      case 5:
        room = new MonologueRoom("They always called me 'The chosen of the Goddess'. I wish I knew why. I never felt special, even now... I am just scared.");
        break;
      case 6:
        room = new BattleRoom();
        enemies.add(new Ghost(600,300));
        enemies.add(new Skull(500,200));
        enemies.add(new Ghost(100,300));
        enemies.add(new Ghost(300,200));
        break;
      case 7:
        room = new BattleRoom();
        enemies.add(new Ghost(600,200));
        enemies.add(new BigGhost(500,200));
        enemies.add(new Ghost(300,200));
        break;
      case 8:
        room = new BattleRoom();
        enemies.add(new BigGhost(600,300));
        enemies.add(new Skull(500,200));
        enemies.add(new Ghost(100,300));
        break;
      case 9:
        room = new MonologueRoom("I\u2019ve lived in the temple since I can remember. \"That doesn\u2019t matter anymore, we live for Her, now.\" - they would say.");
        break;
      case 10:
        room = new BattleRoom();
        enemies.add(new Skull(600,300));
        enemies.add(new Skull(500,200));
        enemies.add(new Skull(100,300));
        break;
      case 11:
        room = new BattleRoom();
        enemies.add(new Ghost(600,300));
        enemies.add(new Skull(500,200));
        enemies.add(new Ghost(300,200));
        enemies.add(new Skull(200,300));
        enemies.add(new Ghost(100,300));
        break;
      case 12:
        room = new BattleRoom();
       // enemies.add(new Skull(600,300));
        enemies.add(new BigGhost(500,200));
        enemies.add(new Ghost(300,200));
        enemies.add(new BigGhost(200,300));
       // enemies.add(new Skull(100,300));
        break;
      case 13:
        room = new BattleRoom();
        enemies.add(new BigGhost(300,300));
        enemies.add(new Ghost(100,200));
        enemies.add(new Ghost(200,200));
        enemies.add(new Ghost(400,200));
        enemies.add(new Ghost(500,200));
        
        break;
      case 14:
        room = new MonologueRoom("All the gifts, 'Intuition', 'Ear', 'Heartwhisper'... All for what? Living in isolation, what were they good for?");
        break;
      case 15:
        room = new BattleRoom();
        enemies.add(new BigGhost(200,300));
        enemies.add(new Skull(200,200));
        enemies.add(new Skull(600,200));
        enemies.add(new Skull(120,500));
        break;
      case 16:
       // room = new MonologueRoom("Those ghosts, they don\u2019t seem evil. They are restless, yes, but I don\u2019t know why...");
        room = new BattleRoom();
        enemies.add(new BigGhost(200,300));
        enemies.add(new BigGhost(600,300));
        enemies.add(new BigGhost(300,400));
        break;
      case 17:
        room = new MonologueRoom("I didn\u2019t even know how much I believed in the Goddess, not until she spoke to me in that dream. But soon, it won't matter.");
        break;
      case 18:
        room = new BattleRoom();
        enemies.add(new KingGhost(width/2, 250));
        enemies.add(new BigGhost(width/2 - 100, 200));
        enemies.add(new BigGhost(width/2 + 100, 200));
        enemies.add(new Ghost(width/2 + 200, 150));
        enemies.add(new Ghost(width/2 - 200, 150));
        break;
      case 19:
        room = new FinalRoom();
        break;
    }
  }
}
class Object {
  float x, y;
  HitBox hitBox;
  int top, bottom, left, right, centerH, centerV, topSensor, top_left, top_right, topSensor_left, topSensor_right, center_left, center_right, bottom_left, bottom_right, center_center, prev_bottom;
  boolean collided;
  
  int spriteOffsetX;
  int spriteOffsetY;
  
  float speedX, speedY;
  
  public void update(){
    //move();
    hitBox.update(x, y);
    collide();
  }
  
  public void move(){
    
  }
  
  public void collide(){
    collided = false;
    if (hitBox.x > level.rightBorder - hitBox.width){
      x = level.rightBorder - hitBox.width - hitBox.offsetX;
      collided = true;
      speedX *= -1; 
    }
    if (hitBox.x < level.leftBorder){
      x = level.leftBorder - hitBox.offsetX;
      collided = true;
      speedX *= -1;
    }
    if (hitBox.y > level.bottomBorder - hitBox.height){
      y = level.bottomBorder - hitBox.height - hitBox.offsetY;
      collided = true;
      speedY *= -1;
    }
    if (hitBox.y < level.topBorder){
      y = level.topBorder - hitBox.offsetY;
      collided = true;
      speedY *= -1;
    }
  }
  
  //void getBounds() {
  //  topSensor=floor((y-(tsize+1))/tsize);
  //  top=floor((y-sizey)/tsize);
  //  centerV=floor((y-sizey/2)/tsize);
  //  bottom=floor(y/tsize);
  //  left=floor(x/tsize);
  //  centerH=floor((x+sizex/2)/tsize);
  //  right=floor((x+sizex)/tsize);

  //  if (left<0)left=0;
  //  if (left>w-1)left=w-1;

  //  if (centerH<0)centerH=0;
  //  if (centerH>w-1)centerH=w-1;

  //  if (right<0)right=0;
  //  if (right>w-1)right=w-1;

  //  if (top<0) top=0;
  //  if (top>h-1) top=h-1;

  //  if (topSensor<0) topSensor=0;
  //  if (topSensor>h-1) topSensor=h-1;

  //  if (centerV<0) centerV=0;
  //  if (centerV>h-1) centerV=h-1;

  //  if (bottom<0) bottom=0;
  //  if (bottom>h-1) bottom=h-1;


  //  top_left=left+w*top;
  //  top_right=right+w*top;
  //  topSensor_left=left+w*topSensor;
  //  topSensor_right=right+w*topSensor;
  //  center_left=left+w*centerV;
  //  center_right=right+w*centerV;
  //  bottom_left=left+w*bottom;
  //  bottom_right=right+w*bottom;
  //  center_center=centerH+w*centerV;
  //}

  //void colide() {
  //  getBounds();
  //  colidiuDir=false;
  //  colidiuEsq=false;
  //  canStand=true;
  //  //pes
  //  if (yspeed>0) {
  //    if (solido(bottom_left)==true || solido(bottom_right)==true) {
  //      caindo = false;
  //      canCancelJump = true;
  //      yspeed = 0;
  //      y = bottom*tsize-1;
  //    }

  //    else if (prev_bottom<bottom && (solidoTransponivel(bottom_left)==true || solidoTransponivel(bottom_right)==true)) {
  //      caindo = false;
  //      canCancelJump = true;
  //      yspeed = 0;
  //      y = bottom*tsize-1;
  //    }
  //  }
  //  //cabe\u00e7a
  //  else if (yspeed<0) {
  //    if (solido(top_right)==true || solido(top_left)==true) {
  //      yspeed = -yspeed*0.1;
  //      y = (top+1)*tsize+sizey;
  //    }
  //    if (climbing==true && escalavel(center_center)==false) {
  //      yspeed = 0;
  //      y = (centerV+1)*tsize+sizey/2;
  //    }
  //  }
  //  if (solido(topSensor_right)==true || solido(topSensor_left)==true) {
  //    canStand=false;
  //  }

  //  x+=xspeed;
  //  getBounds();

  //  //esq
  //  if (xspeed<0) {
  //    if (solido(top_left)==true || solido(center_left)==true || solido(bottom_left)==true) {
  //      x = (left+1)*tsize;
  //      xspeed=0;
  //      colidiuEsq=true;
  //    }
  //  }
  //  //dir
  //  else if (xspeed>0) {
  //    if (solido(top_right)==true || solido(center_right)==true || solido(bottom_right)==true) {
  //      x = (right)*tsize-sizex-1;
  //      xspeed=0;
  //      colidiuDir=true;
  //    }
  //  }

  //  if (hasItem[7] && input.up.isDown==true && escalavel(center_center)==true && climbing==false) {
  //    startClimbing();
  //  }
  //  if (climbing==true && escalavel(center_center)==false)stopClimbing();
  //  if (agua(center_center)) {
  //    if (agua==false) setWaterBehavior();
  //  }
  //  else {
  //    if (agua==true) setLandBehavior();
  //  }

  //  prev_bottom=bottom;
  //}  
  
}
class Player extends Object {
  float speed = 5;
  float angle;
  int vectorX, vectorY;
  int hp, maxHP = 3;
  int facingDirection = 1;
  
  int throwCoolDown;
  
  int flinch, takingDamage;
  
  //String state;
  
  Player() {
    x = width/2;
    y = level.bottomBorder - 60;
    //state = "teste";
    hp = maxHP;
    
    spriteOffsetX = -redIdle[0].width/2;
    spriteOffsetY = -redIdle[0].height;

    hitBox = new HitBox (-30, -15, 60, 30);
  }
  
  public void update(){   
    if (takingDamage > 0) {
      x -= facingDirection * 5;
      takingDamage --;
      if (takingDamage == 0 && hp <= 0){
        fade.start(120, color(211, 32, 32), "gameOver");
      }
    }
    else {
      //switch (state){
      //  case "searching":
      //    break;
      //  case "following":
      //    break; 
      //}
      facingDirection = -1;
      if (x < mouseX) facingDirection = 1;
      move();
      if (input.mouseLeft.justPressed){
        if (candles.size() < candleMax && throwCoolDown <= 0) {
          throwCoolDown = 6;
          SE.trigger(SE.flick);
        }
        else{
          SE.trigger(SE.noCandles);
        }
      }
      if (throwCoolDown > 0){
        throwCoolDown --;
        if (throwCoolDown == 0){
          throwCandle();
        }
      }
    }
    if (flinch > 0){
      flinch--;
    }
    
    super.update();
      
  }
  
  public void move(){
    vectorX = 0;
    vectorY = 0;
    
    if (input.up.isDown) vectorY = -1;
    if (input.down.isDown) vectorY = +1;
    if (input.left.isDown) vectorX = -1;
    if (input.right.isDown) vectorX = +1;
    
    angle = atan2(vectorY, vectorX);
    
    if (vectorX != 0 || vectorY != 0){
      x+=cos(angle)*speed;
      y+=sin(angle)*speed;
    }
  }
  
  public void throwCandle(){
    
    float throwVectorX = mouseX - x;
    float throwVectorY = mouseY - y;
    float throwAngle = atan2(throwVectorY, throwVectorX);
    
    float throwDistance = min(maxDist, dist(x, y, mouseX, mouseY));
    
    float targetX = x + throwDistance * cos(throwAngle);
    float targetY = y + throwDistance * sin(throwAngle);
    
    Candle c = new Candle(x, y, targetX, targetY);

    candles.add(c);
    //fill(255,0,0);
    //rect(targetX, targetY, 40, 40);
  }
  
  public void takeDamage(int d){
    SE.trigger(SE.damagePlayer);
    hp -=d;
    if (hp < 0) hp = 0;
    takingDamage = 8;
    flinch = playerFlinchTime;
    throwCoolDown = 0;
  }
  
  public void render(){
    
    if (takingDamage > 0) tint(255,200,200);
    if (flinch <= 0 || takingDamage > 0 || animation % 2 == 0){
      if (throwCoolDown > 0) imageX(redThrow[0],x + spriteOffsetX, y + spriteOffsetY, (facingDirection == 1));
      else if (vectorX != 0 || vectorY != 0) imageX(redWalk[animation/10 % 4],x + spriteOffsetX, y + spriteOffsetY, (facingDirection == 1));
      else imageX(redIdle[ animation/10 % 4 ],x + spriteOffsetX, y + spriteOffsetY, (facingDirection == 1));
      rect(x,y,1,1);
    }
    tint(255);
    
    //hitBox.render();
    
    imageMode(CENTER);
    if (candles.size() >= candleMax) {
      tint(211, 32, 32);
    }
    image(cursorImg, mouseX, mouseY);
    noTint();
    imageMode(CORNER);
    
    //rectMode(CENTER);
    //fill(255);
    //rect(mouseX, mouseY, 4, 4);
  }
  
  //float getAimAngle(){
  //  float a = atan2(mouseY - y, mouseX - x);
  //  return a;
  //}
}

public void renderHUD(){
  image(hudHeader,10,10);
  for (int i = 0; i < player.maxHP; i++) {
    if (i +1 > player.hp) tint (20);
    image(hudHeart, 15 + i * 30, 45);
    noTint();
  }
  for (int i = 0; i < candleMax; i++) {
    if (i + 1 > candleMax - candles.size()) tint (20);
    image(hudCandle, width -25 - i * 20, 15);
    noTint();
  }
  fill(255);
  text("Floor "+(currentRoom + 1),20, height-20);
}
class Room {
  Door entranceDoor;
  Door exitDoor;
  
  Room(){
    
  }
  public void render(){
    image(bgImg, 0, 0);
    renderDoors();
  }
  public void renderDoors(){
    entranceDoor.render();
    exitDoor.render();
  }
  public void update(){
    entranceDoor.update();
    exitDoor.update();
    if (!entranceDoor.locked){
      entranceDoor.lock();
    }
  }
}

class TutorialRoom extends Room {
  TutorialRoom(){
    setBGM("mus_gameplay");
    entranceDoor = new Door (false,false,false);
    exitDoor = new Door (true,false,true);
  }
  public void render(){
    image(tutorialImg, 0, 0);
    renderDoors();
  }
  public void update(){
    super.update();
    exitDoor.unlock();
  }
}

class BattleRoom extends Room {
  BattleRoom(){
    setBGM("mus_gameplay");
    entranceDoor = new Door (false,false,true);
    exitDoor = new Door (true,true,true);
  }
  public void update(){
    super.update();
    if (enemies.size() == 0 && exitDoor.locked == true) {
      exitDoor.unlock();
      SE.trigger(SE.gateOpen);
    }
  }
}

class MonologueRoom extends Room {
  String monologue;
  int currentCharacter;
  MonologueRoom(String m){
    setBGM("mus_muted");
    entranceDoor = new Door (false,false,true);
    exitDoor = new Door (true,false,true);
    monologue = m;
    currentCharacter = 0;
  }
  public void update(){
    super.update();
    if (currentCharacter < monologue.length()) {
      if (animation % 3 == 0) currentCharacter++;
    }
    //else if (exitDoor.locked) {
      
    //}
  }
  public void render(){
    super.render();
    fill(255);
    textLeading(28);
    text(monologue.substring(0, currentCharacter), 200, 200, width - 2 * 200, 100);
  }
}

class FinalRoom extends Room {
  FinalRoom(){
    //setBGM("mus_gameplay");
    BGMPlayer.shiftVolume(BGMPlayer.getVolume(), 0, 1000);
    entranceDoor = new Door (false,false,true);
    exitDoor = new Door (false,false,false);
  }
  public void render(){
    image(lastRoomImg, 0, 0);
    renderDoors();
  }
  public void update(){
    if (player.y < 400 && !fade.fading){
      SE.trigger(SE.menuConfirm);
      fade.start(120, 255, "endGame");
    }
  }
}




class Door extends Object{
  boolean isExit, locked, active;
  Door(boolean isExit, boolean locked, boolean active){
    this.isExit = isExit;
    this.locked = locked;
    this.active = active;
    x = width/2;
    if (isExit) {
      y = level.topBorder;
      hitBox = new HitBox(-20, -10, 40, 10);
    }
    else {
      y = level.bottomBorder;
      hitBox = new HitBox(-20, 0, 40, 10);
    }
    spriteOffsetX = -doorLocked.width/2; 
  }
  
  public void render(){
    if (active){
      pushMatrix();
      translate(x, y);
      if (!isExit) scale (1, -1);
      translate(0,-56);
      image(locked ? doorLocked : doorUnlocked, spriteOffsetX, 0);
      popMatrix();
    }
    //hitBox.render();
  }
  public void update(){
    hitBox.update(x, y);
    if (isExit && !locked && player.hitBox.overlap(hitBox)) {
      fade.start(30, 0, "nextRoom");
    }
  }
  public void lock(){
    SE.trigger(SE.door);
    locked = true;
  }
  public void unlock(){
    SE.trigger(SE.door);
    locked = false;
  }
}
class SE {
  AudioSample flick;
  AudioSample ignite;
  AudioSample damageEnemy;
  AudioSample damagePlayer;
  AudioSample openDoor;
  AudioSample noCandles;
  AudioSample collect;
  AudioSample menuConfirm;
  AudioSample gateOpen;
  AudioSample door;

  public void initAllSounds() {
    flick = minim.loadSample("Assets/Sound/snd_ingame_throw_candle_01.wav");
    ignite = minim.loadSample("Assets/Sound/snd_triangle.wav");
    damageEnemy = minim.loadSample("Assets/Sound/snd_enemy_take_damage_01.wav");
    damagePlayer = minim.loadSample("Assets/Sound/snd_hero_take_damage_01.wav");
    openDoor = minim.loadSample("Assets/Sound/snd_ingame_gate_open.wav");
    noCandles = minim.loadSample("Assets/Sound/snd_no_more_candles.wav");
    collect = minim.loadSample("Assets/Sound/snd_collect_candle.wav");
    menuConfirm = minim.loadSample("Assets/Sound/snd_menu_confirm.wav");
    gateOpen = minim.loadSample("Assets/Sound/snd_ingame_gate_open.wav");
    door = minim.loadSample("Assets/Sound/snd_generic_button.wav");
  }
  public void trigger(AudioSample a) {
    a.trigger();
  }
  public void stop(AudioSample a) {
    a.stop();
  }

  public void closeAll() {
    flick.close();
    ignite.close();
    damageEnemy.close();
    damagePlayer.close();
    openDoor.close();
    noCandles.close();
    collect.close();
    menuConfirm.close();
    gateOpen.close();
    door.close();
  }
}

public void stop()
{
  SE.closeAll();
  BGMPlayer.close();
  minim.stop();
  super.stop();
}

String currentBGM;
public void setBGM(String m){
  if (!m.equals(currentBGM)){
    BGMPlayer.pause();
    BGMPlayer = minim.loadFile("Assets/sound/"+m+".wav");
    BGMPlayer.loop();
    currentBGM = m;
  }
}

String currentBGS;
public void setBGS(String m){
  if (!m.equals(currentBGS)){
    BGSPlayer.pause();
    BGSPlayer = minim.loadFile("Assets/sound/"+m+".wav");
    BGSPlayer.loop();
    currentBGM = m;
  }
}
  public void settings() {  size (800, 600); }
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "--present", "--window-color=#666666", "--hide-stop", "Rite_of_the_Goddess" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
