//=============================================================================
// Yanfly Engine Plugins - Event Chase Player
// YEP_EventChasePlayer.js
//=============================================================================

var Imported = Imported || {};
Imported.YEP_EventChasePlayer = true;

var Yanfly = Yanfly || {};
Yanfly.ECP = Yanfly.ECP || {};
Yanfly.ECP.version = 1.07;

//=============================================================================
/*:
 * @plugindesc v1.07 When a player is in the proximity of a certain event,
 * the event will start chasing or fleeing from the player.
 * @author Yanfly Engine Plugins
 *
 * @param Sight Lock
 * @type number
 * @min 0
 * @desc This is the number of frames for how long an event chases
 * the player if 'this._seePlayer = true' is used.
 * @default 300
 *
 * @param See Player
 * @type boolean
 * @on YES
 * @off NO
 * @desc Does the event have to be able to see the player by default?
 * NO - false     YES - true
 * @default true
 *
 * @param Alert Timer
 * @type number
 * @min 0
 * @desc This is the number of frames that must occur before the
 * alert balloon will show up on the same event.
 * @default 120
 *
 * @param Alert Balloon
 * @type number
 * @min 0
 * @desc This is the default balloon used when the player is seen.
 * Refer to balloon ID's.
 * @default 1
 *
 * @param Alert Sound
 * @type file
 * @dir audio/se/
 * @require 1
 * @desc This is the default sound played when the player is seen.
 * @default Attack1
 *
 * @param Alert Common Event
 * @type common_event
 * @desc The default common event played when the player is seen.
 * Use 0 if you do not wish to use a Common Event.
 * @default 0
 *
 * @param Return After
 * @type boolean
 * @on YES
 * @off NO
 * @desc After chasing/fleeing from a player, the event returns
 * to its original spot. NO - false   YES - true
 * @default true
 *
 * @param Return Wait
 * @type number
 * @min 0
 * @desc The frames to wait after finishing a chase/flee.
 * @default 180
 *
 * @help
 * ============================================================================
 * Introduction
 * ============================================================================
 *
 * This plugin allows you to make events that will chase the player or flee
 * from the player when the player enters within range of the event or when the
 * event sees the player.
 *
 * ============================================================================
 * How to Use
 * ============================================================================
 *
 * Insert these lines into the script call window within the Movement Route
 * event to give an event the chase or flee flag.
 *
 * Note: This doesn’t work with players.
 *
 * Script Call lines
 *  this._chaseRange = x       Event will chase player if reaching x range.
 *  this._fleeRange = x        Event will flee from player if reaching x range.
 *  this._chaseSpeed = x       Event's movement speed when chasing.
 *  this._fleeSpeed = x        Event's movement speed when fleeing.
 *  this._sightLock = x        Event will flee/chase player for x frames.
 *  this._seePlayer = true     Requires the event to be able to see player.
 *  this._seePlayer = false    Doesn't require event to be able to see player.
 *  this._alertBalloon = x     This balloon will play when player is seen.
 *  this._alertSound = x       This sound will play when player is seen.
 *  this._alertSoundVol = x    The volume used by the alert sound.
 *  this._alertSoundPitch = x  The pitch used by the alert sound.
 *  this._alertSoundPan = x    The pan used by the alert sound.
 *  this._alertCommonEvent = x This event will play when player is seen.
 *  this._returnAfter = true   Returns the event back to its original spot.
 *  this._returnAfter = false  Event stays where it is when finished chasing.
 *  this._returnWait = x       How long event waits after finishing chase/flee.
 *
 * It is best to play this inside of a custom move route for the event at a
 * high frequency rate. Keep in mind these effects only occur after the setting
 * is made and ran, which means upon loading a map, if the event with a low
 * frequency hasn't loaded up 'this._chaseRange = x' in its movement queue yet,
 * the event will not chase the player just yet.
 *
 * ============================================================================
 * Changelog
 * ============================================================================
 *
 * Version 1.07:
 * - Added a background mechanic to stagger an event if they're chasing the
 * player and get caught behind an object. This will prevent the event from
 * continuously chasing the player and dropping the FPS rate.
 *
 * Version 1.06:
 * - Updated for RPG Maker MV version 1.5.0.
 *
 * Version 1.05:
 * - Optimization update.
 *
 * Version 1.04:
 * - Fixed a bug with this._seePlayer causing them to see stealthed players.
 *
 * Version 1.03:
 * - Improved pathfinding for chasing events. They will get stuck less by walls
 * and/or events that may be blocking the event.
 * - Added random factor for fleeing events. Fleeing events won't simply just
 * run away 180 degrees away from the player. They will sometimes move in a
 * random direction.
 *
 * Version 1.02:
 * - Added 'Return After' parameter where events will return to their original
 * spot after chasing/fleeing from a player.
 * - Added 'Return Wait' parameter to determine how long an event will wait in
 * place before returning after a finished chase/flee.
 * - Added 'this._returnAfter' and 'this._returnWait' to the list of available
 * movement route script calls.
 *
 * Version 1.01:
 * - Added 'this._alertSoundPitch' 'this._alertSoundVol' 'this._alertSoundPan'
 * to the settings you can alter to adjust the alert sound.
 *
 * Version 1.00:
 * - Finished Plugin!
 */
//=============================================================================

//=============================================================================
// Parameter Variables
//=============================================================================

Yanfly.Parameters = PluginManager.parameters("YEP_EventChasePlayer");
Yanfly.Param = Yanfly.Param || {};

Yanfly.Param.ECPSightLock = Number(Yanfly.Parameters["Sight Lock"]);
Yanfly.Param.ECPSeePlayer = String(Yanfly.Parameters["See Player"]);
Yanfly.Param.ECPSeePlayer = eval(Yanfly.Param.ECPSeePlayer);
Yanfly.Param.ECPAlertTimer = Number(Yanfly.Parameters["Alert Timer"]);
Yanfly.Param.ECPAlertBalloon = Number(Yanfly.Parameters["Alert Balloon"]);
Yanfly.Param.ECPAlertSound = String(Yanfly.Parameters["Alert Sound"]);
Yanfly.Param.ECPAlertEvent = Number(Yanfly.Parameters["Alert Common Event"]);
Yanfly.Param.ECPReturn = eval(String(Yanfly.Parameters["Return After"]));
Yanfly.Param.ECPReturnWait = Number(Yanfly.Parameters["Return Wait"]);

//=============================================================================
// Main Code
//=============================================================================

Yanfly.ECP.Game_Event_setupPage = Game_Event.prototype.setupPage;
Game_Event.prototype.setupPage = function () {
  Yanfly.ECP.Game_Event_setupPage.call(this);
  this.clearChaseSettings();
};

Game_Event.prototype.clearChaseSettings = function () {
  this._alertBalloon = Yanfly.Param.ECPAlertBalloon;
  this._alertCommonEvent = Yanfly.Param.ECPAlertEvent;
  this._alertLock = 0;
  this._alertPlayer = false;
  this._alertSound = Yanfly.Param.ECPAlertSound;
  this._alertSoundVol = 100;
  this._alertSoundPitch = 100;
  this._alertSoundPan = 0;
  this._alertTimer = 0;
  this._chasePlayer = false;
  this._chaseRange = 0;
  this._chaseSpeed = this._moveSpeed;
  this._defaultSpeed = this._moveSpeed;
  this._fleePlayer = false;
  this._fleeRange = 0;
  this._fleeSpeed = this._moveSpeed;
  this._seePlayer = Yanfly.Param.ECPSeePlayer;
  this._sightLock = Yanfly.Param.ECPSightLock;
  this._returnAfter = Yanfly.Param.ECPReturn;
  this._returnWait = Yanfly.Param.ECPReturnWait;
  this._returnPhase = false;
  this._returnFrames = 0;
  this._staggerCount = 0;
  this._startLocationX = this.x;
  this._startLocationY = this.y;
  this._startLocationDir = this._direction;
};

Yanfly.ECP.Game_Event_updateSelfMovement =
  Game_Event.prototype.updateSelfMovement;
Game_Event.prototype.updateSelfMovement = function () {
  if (Imported.YEP_StopAllMove && $gameSystem.isEventMoveStopped()) return;
  this.updateChaseDistance();
  this.updateFleeDistance();
  this.updateChaseMovement();
};

Yanfly.ECP.Game_Event_update = Game_Event.prototype.update;
Game_Event.prototype.update = function () {
  Yanfly.ECP.Game_Event_update.call(this);
  this.updateAlert();
  this.updateReturnPhase();
};

Game_Event.prototype.canSeePlayer = function () {
  if (!this._seePlayer) return false;
  var sx = this.deltaXFrom($gamePlayer.x);
  var sy = this.deltaYFrom($gamePlayer.y);
  if (Math.abs(sx) > Math.abs(sy)) {
    var direction = sx > 0 ? 4 : 6;
  } else {
    var direction = sy > 0 ? 8 : 2;
  }
  if (direction === this.direction()) {
    this._alertLock = this._sightLock;
    return true;
  }
  return false;
};

Game_Event.prototype.updateChaseDistance = function () {
  if (this._erased) return;
  if (this._chaseRange <= 0) return;
  var dis = Math.abs(this.deltaXFrom($gamePlayer.x));
  dis += Math.abs(this.deltaYFrom($gamePlayer.y));
  if (this.chaseConditions(dis)) {
    this.startEventChase();
  } else if (this._chasePlayer) {
    this.endEventChase();
  }
};

Game_Event.prototype.chaseConditions = function (dis) {
  if (dis <= this._chaseRange && this.nonSeePlayer()) {
    this._alertLock = this._sightLock;
    return true;
  }
  if (this._alertLock > 0) return true;
  if (dis <= this._chaseRange && this.canSeePlayer()) return true;
  return false;
};

Game_Event.prototype.nonSeePlayer = function () {
  if (Imported.YEP_X_EventChaseStealth) {
    if (this.meetStealthModeConditions()) {
      this.stealthClearChaseSettings();
      this._stopCount = 0;
      return false;
    }
  }
  return !this._seePlayer;
};

Game_Event.prototype.startEventChase = function () {
  this._chasePlayer = true;
  this.setMoveSpeed(this._chaseSpeed);
};

Game_Event.prototype.endEventChase = function () {
  this._chasePlayer = false;
  this.setMoveSpeed(this._defaultSpeed);
  if (this._alertTimer <= 0) this._alertPlayer = false;
  this.startReturnPhase();
};

Game_Event.prototype.updateFleeDistance = function () {
  if (this._erased) return;
  if (this._fleeRange <= 0) return;
  var dis = Math.abs(this.deltaXFrom($gamePlayer.x));
  dis += Math.abs(this.deltaYFrom($gamePlayer.y));
  if (this.fleeConditions(dis)) {
    this.startEventFlee();
  } else if (this._fleePlayer) {
    this.endEventFlee();
  }
};

Game_Event.prototype.fleeConditions = function (dis) {
  if (this._alertLock > 0) return true;
  if (dis <= this._fleeRange && this.canSeePlayer()) return true;
  if (dis <= this._fleeRange && !this._seePlayer) {
    this._alertLock = this._sightLock;
    return true;
  }
  return false;
};

Game_Event.prototype.startEventFlee = function () {
  this._fleePlayer = true;
  this.setMoveSpeed(this._fleeSpeed);
};

Game_Event.prototype.endEventFlee = function () {
  this._fleePlayer = false;
  this.setMoveSpeed(this._defaultSpeed);
  if (this._alertTimer <= 0) this._alertPlayer = false;
  this.startReturnPhase();
};

Game_Event.prototype.updateChaseMovement = function () {
  if (this._staggerCount > 0) {
    return this._staggerCount--;
  }
  if (this._stopCount > 0 && this._chasePlayer) {
    var direction = this.findDirectionTo($gamePlayer.x, $gamePlayer.y);
    if (direction > 0) {
      var x = this._x;
      var y = this._y;
      this.moveStraight(direction);
      if (x === this._x && y === this._y) this._staggerCount = 20;
    }
  } else if (this._stopCount > 0 && this._fleePlayer) {
    this.updateFleeMovement();
  } else if (this._returnPhase) {
    this.updateMoveReturnAfter();
  } else {
    Yanfly.ECP.Game_Event_updateSelfMovement.call(this);
  }
};

Game_Event.prototype.updateFleeMovement = function () {
  switch (Math.randomInt(6)) {
    case 0:
    case 1:
    case 2:
    case 3:
    case 4:
      this.moveAwayFromPlayer();
      break;
    case 5:
      this.moveRandom();
      break;
  }
};

Game_Event.prototype.updateAlert = function () {
  if (this._erased) return;
  this._alertLock--;
  if (this.alertConditions()) this.activateAlert();
  if (this._alertPlayer) this._alertTimer--;
};

Game_Event.prototype.alertConditions = function () {
  return (this._chasePlayer || this._fleePlayer) && !this._alertPlayer;
};

Game_Event.prototype.activateAlert = function () {
  if (this._alertBalloon >= 0) this.requestBalloon(this._alertBalloon);
  this._alertPlayer = true;
  this._alertTimer = Yanfly.Param.ECPAlertTimer;
  this.playAlertSound();
  this.playAlertCommonEvent();
};

Game_Event.prototype.playAlertSound = function () {
  if (this._alertSound === "") return;
  var sound = {
    name: this._alertSound,
    volume: this._alertSoundVol,
    pitch: this._alertSoundPitch,
    pan: this._alertSoundPan,
  };
  AudioManager.playSe(sound);
};

Game_Event.prototype.playAlertCommonEvent = function () {
  if (this._alertCommonEvent <= 0) return;
  $gameTemp.reserveCommonEvent(this._alertCommonEvent);
};

Game_Event.prototype.startReturnPhase = function () {
  if (!this._returnAfter) return;
  this._returnPhase = true;
  this._returnFrames = this._returnWait;
};

Game_Event.prototype.updateReturnPhase = function () {
  if (this._returnPhase) this._returnFrames--;
};

Game_Event.prototype.updateMoveReturnAfter = function () {
  if (this._returnFrames > 0) return;
  var sx = this.deltaXFrom(this._startLocationX);
  var sy = this.deltaYFrom(this._startLocationY);
  if (Math.abs(sx) > Math.abs(sy)) {
    if (Math.randomInt(6) <= 4) {
      this.moveStraight(sx > 0 ? 4 : 6);
      if (!this.isMovementSucceeded() && sy !== 0) {
        this.moveStraight(sy > 0 ? 8 : 2);
      }
    } else {
      this.moveRandom();
    }
  } else if (sy !== 0) {
    if (Math.randomInt(6) <= 4) {
      this.moveStraight(sy > 0 ? 8 : 2);
      if (!this.isMovementSucceeded() && sx !== 0) {
        this.moveStraight(sx > 0 ? 4 : 6);
      }
    } else {
      this.moveRandom();
    }
  }
  if (sx === 0 && sy === 0) {
    this._returnPhase = false;
    this._returnFrames = 0;
    this._direction = this._startLocationDir;
  }
};

//=============================================================================
// End of File
//=============================================================================
