/*:
 * @target MZ
 * @plugindesc HUD map néon — leader + mini portraits — fade after stop (0.5s) — scalable for many heroes
 * @author Bertran
 * @help
 * Installation :
 * - Colle ce fichier en tant que TF_HUD_Map.js dans /js/plugins/
 * - Active le plugin dans le Plugin Manager
 * - Place les images dans /img/pictures/ (voir la liste ci‑dessous)
 *
 * Images attendues (placeholders acceptés) :
 * - hud_silhouette.png        (silhouette générique, fond transparent)
 * - hud_frame.png             (cadre/plateau du HUD, fond transparent)
 * - hud_mini_frame.png        (cadre mini portrait)
 *
 * Le plugin utilise les faces/portraits des acteurs si disponibles.
 * Par défaut, couleurs néon (hex) :
 *  - leader1 (Kou)   : #FFD166 (or)
 *  - leader2 (Sakura): #00E5FF (cyan)
 *  - leader3 (Ken)   : #FF2D95 (fuchsia)
 *  - leader4 (Asuka) : #FF3B3B (red)
 *
 * Le HUD apparaît 0.5s après immobilité et disparaît au mouvement.
 *
 * Paramètres internes modifiables dans OP_CONFIG ci‑dessous.
 */

(() => {
  const PLUGIN_NAME = "TF_HUD_Map";

  // -------------------------
  // CONFIG
  // -------------------------
  const OP_CONFIG = {
    x: 24, // position X (haut gauche)
    y: 24, // position Y (haut gauche)
    leaderSize: 140, // taille du grand portrait (px)
    miniSize: 56, // taille mini portraits
    miniSpacing: 8,
    idleDelay: 30, // frames (0.5s @60fps)
    fadeDuration: 18, // frames for fade in/out
    slideDistance: 18, // px slide on appear/disappear
    silhouetteImage: "hud_silhouette",
    frameImage: "hud_frame",
    miniFrameImage: "hud_mini_frame",
    colors: [
      "#FFD166", // leader 1 - gold
      "#00E5FF", // leader 2 - cyan
      "#FF2D95", // leader 3 - fuchsia
      "#FF3B3B"  // leader 4 - red
    ],
    maxMiniToShow: 3, // number of mini portraits under leader
    hideInBattle: true,
    hideInMenu: true,
    hideInMessage: true
  };

  // -------------------------
  // Utilities
  // -------------------------
  function hexToRgb(hex) {
    const h = hex.replace("#", "");
    const bigint = parseInt(h, 16);
    return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255];
  }

  // -------------------------
  // Hook into Scene_Map
  // -------------------------
  const _Scene_Map_createAllWindows = Scene_Map.prototype.createAllWindows;
  Scene_Map.prototype.createAllWindows = function() {
    _Scene_Map_createAllWindows.call(this);
    if (!this._tfHud) {
      this._tfHud = new TF_HUD();
      this.addChild(this._tfHud);
    }
  };

  // -------------------------
  // HUD Sprite Container
  // -------------------------
  function TF_HUD() {
    this.initialize(...arguments);
  }

  TF_HUD.prototype = Object.create(Sprite.prototype);
  TF_HUD.prototype.constructor = TF_HUD;

  TF_HUD.prototype.initialize = function() {
    Sprite.prototype.initialize.call(this);
    this.x = OP_CONFIG.x - OP_CONFIG.slideDistance;
    this.y = OP_CONFIG.y;
    this.opacity = 0;
    this._visibleState = false; // currently shown
    this._idleCounter = 0;
    this._fadeTimer = 0;
    this._creating = true;
    this._sprites = {};
    this.createBase();
    this._creating = false;
  };

  TF_HUD.prototype.createBase = function() {
    // frame background
    const frame = new Sprite(ImageManager.loadPicture(OP_CONFIG.frameImage));
    frame.anchor.x = 0;
    frame.anchor.y = 0;
    frame.x = 0;
    frame.y = 0;
    frame.opacity = 0;
    this.addChild(frame);
    this._sprites.frame = frame;

    // leader silhouette (base)
    const leader = new Sprite(ImageManager.loadPicture(OP_CONFIG.silhouetteImage));
    leader.anchor.x = 0;
    leader.anchor.y = 0;
    leader.x = 8;
    leader.y = 8;
    leader.opacity = 0;
    leader.scale.x = OP_CONFIG.leaderSize / Math.max(leader.bitmap.width || OP_CONFIG.leaderSize, 1);
    leader.scale.y = OP_CONFIG.leaderSize / Math.max(leader.bitmap.height || OP_CONFIG.leaderSize, 1);
    this.addChild(leader);
    this._sprites.leader = leader;

    // leader portrait overlay (for actor face if available)
    const leaderPortrait = new Sprite();
    leaderPortrait.anchor.x = 0;
    leaderPortrait.anchor.y = 0;
    leaderPortrait.x = leader.x;
    leaderPortrait.y = leader.y;
    leaderPortrait.opacity = 0;
    this.addChild(leaderPortrait);
    this._sprites.leaderPortrait = leaderPortrait;

    // name / level text
    const nameSprite = new Sprite();
    nameSprite.x = OP_CONFIG.leaderSize + 16;
    nameSprite.y = 16;
    this.addChild(nameSprite);
    this._sprites.name = nameSprite;

    // hp/mp bars container
    const bars = new Sprite();
    bars.x = OP_CONFIG.leaderSize + 16;
    bars.y = 48;
    bars.opacity = 0;
    this.addChild(bars);
    this._sprites.bars = bars;

    // mini portraits container
    this._sprites.minis = [];
    for (let i = 0; i < OP_CONFIG.maxMiniToShow; i++) {
      const mFrame = new Sprite(ImageManager.loadPicture(OP_CONFIG.miniFrameImage));
      mFrame.anchor.x = 0;
      mFrame.anchor.y = 0;
      mFrame.x = 8;
      mFrame.y = OP_CONFIG.leaderSize + 12 + i * (OP_CONFIG.miniSize + OP_CONFIG.miniSpacing);
      mFrame.opacity = 0;
      this.addChild(mFrame);

      const miniSil = new Sprite(ImageManager.loadPicture(OP_CONFIG.silhouetteImage));
      miniSil.anchor.x = 0;
      miniSil.anchor.y = 0;
      miniSil.x = mFrame.x + 6;
      miniSil.y = mFrame.y + 6;
      miniSil.opacity = 0;
      miniSil.scale.x = OP_CONFIG.miniSize / Math.max(miniSil.bitmap.width || OP_CONFIG.miniSize, 1);
      miniSil.scale.y = OP_CONFIG.miniSize / Math.max(miniSil.bitmap.height || OP_CONFIG.miniSize, 1);
      this.addChild(miniSil);

      const miniPortrait = new Sprite();
      miniPortrait.anchor.x = 0;
      miniPortrait.anchor.y = 0;
      miniPortrait.x = miniSil.x;
      miniPortrait.y = miniSil.y;
      miniPortrait.opacity = 0;
      this.addChild(miniPortrait);

      this._sprites.minis.push({
        frame: mFrame,
        sil: miniSil,
        portrait: miniPortrait
      });
    }
  };

  // -------------------------
  // Update loop
  // -------------------------
  TF_HUD.prototype.update = function() {
    Sprite.prototype.update.call(this);
    if (this._creating) return;
      this._visibleState = true; // TEST : force le HUD à être visible

    // auto-hide conditions
    if (this.shouldForceHide()) {
      this.forceHide();
      return;
    }

    // movement detection
    if (this.playerIsMoving()) {
      this._idleCounter = 0;
      if (this._visibleState) this.startFadeOut();
    } else {
      this._idleCounter++;
      if (!this._visibleState && this._idleCounter >= OP_CONFIG.idleDelay) {
        this.startFadeIn();
      }
    }

    // fade handling
    this.updateFade();

    // update content if visible (or during fade)
    if (this._visibleState || this._fadeTimer > 0) {
      this.updateContent();
    }
  };

TF_HUD.prototype.playerIsMoving = function() {
    return $gamePlayer.isMoving();
};

 TF_HUD.prototype.shouldForceHide = function() {
    if (OP_CONFIG.hideInBattle && $gameParty.inBattle()) return true;
    if (OP_CONFIG.hideInMenu && SceneManager._scene instanceof Scene_MenuBase) return true;
    if (OP_CONFIG.hideInMessage && $gameMessage.isBusy()) return true;
    if ($gamePlayer.isTransferring()) return true;
    return false;
};

  TF_HUD.prototype.forceHide = function() {
    this._idleCounter = 0;
    if (this._visibleState || this.opacity > 0) {
      this._visibleState = false;
      this._fadeTimer = OP_CONFIG.fadeDuration;
      this._fadeDirection = -1;
    }
  };

  TF_HUD.prototype.startFadeIn = function() {
    this._visibleState = true;
    this._fadeTimer = OP_CONFIG.fadeDuration;
    this._fadeDirection = 1;
    // slide start offset
    this.x = OP_CONFIG.x - OP_CONFIG.slideDistance;
  };

  TF_HUD.prototype.startFadeOut = function() {
    this._visibleState = false;
    this._fadeTimer = OP_CONFIG.fadeDuration;
    this._fadeDirection = -1;
  };

  TF_HUD.prototype.updateFade = function() {
    if (this._fadeTimer > 0) {
      const d = this._fadeDirection;
      const t = this._fadeTimer;
      const total = OP_CONFIG.fadeDuration;
      const progress = (total - t + 1) / total;
      if (d === 1) {
        this.opacity = Math.min(255, Math.floor(255 * progress));
        this.x = OP_CONFIG.x - Math.floor(OP_CONFIG.slideDistance * (1 - progress));
      } else {
        this.opacity = Math.max(0, Math.floor(255 * (1 - progress)));
        this.x = OP_CONFIG.x - Math.floor(OP_CONFIG.slideDistance * progress);
      }
      this._fadeTimer--;
      if (this._fadeTimer === 0 && this._fadeDirection === -1) {
        // ensure fully hidden
        this.opacity = 0;
        this.x = OP_CONFIG.x - OP_CONFIG.slideDistance;
      } else if (this._fadeTimer === 0 && this._fadeDirection === 1) {
        this.opacity = 255;
        this.x = OP_CONFIG.x;
      }
    }
  };

  // -------------------------
  // Content update
  // -------------------------
  TF_HUD.prototype.updateContent = function() {
    const party = $gameParty.members();
    if (!party || party.length === 0) return;

    // leader is first in party
    const leader = party[0];
    this.updateLeader(leader);
    this.updateMinis(party.slice(1, 1 + OP_CONFIG.maxMiniToShow));
  };

  TF_HUD.prototype.updateLeader = function(actor) {
    if (!actor) return;
    const idx = $gameParty.members().indexOf(actor);
    const color = OP_CONFIG.colors[idx] || OP_CONFIG.colors[0];
    const rgb = hexToRgb(color);

    // apply glow via blend color on silhouette
    const sil = this._sprites.leader;
    sil.bitmap = ImageManager.loadPicture(OP_CONFIG.silhouetteImage);
    sil.setBlendColor([rgb[0], rgb[1], rgb[2], 160]); // alpha for glow
    sil.opacity = this.opacity;

    // try to load actor face or face image as portrait overlay
    const portrait = this._sprites.leaderPortrait;
    const faceName = actor.faceName();
    if (faceName) {
      portrait.bitmap = ImageManager.loadFace(faceName);
      portrait.scale.x = (OP_CONFIG.leaderSize / 144) * 2; // approximate scale
      portrait.scale.y = (OP_CONFIG.leaderSize / 144) * 2;
      portrait.opacity = this.opacity;
    } else {
      portrait.bitmap = null;
      portrait.opacity = 0;
    }

    // name + level
    const nameSprite = this._sprites.name;
    nameSprite.bitmap = new Bitmap(300, 48);
    nameSprite.bitmap.clear();
    nameSprite.bitmap.fontSize = 20;
    nameSprite.bitmap.textColor = "#FFFFFF";
    nameSprite.bitmap.drawText(actor.name(), 0, 0, 300, 24, "left");
    nameSprite.bitmap.fontSize = 16;
    nameSprite.bitmap.textColor = color;
    nameSprite.bitmap.drawText("Lv " + actor.level, 0, 24, 300, 20, "left");
    nameSprite.opacity = this.opacity;

    // HP / MP bars
    const bars = this._sprites.bars;
    const w = 220;
    const h = 10;
    bars.bitmap = new Bitmap(w, h * 2 + 6);
    bars.bitmap.clear();
    // HP
    const hpRate = actor.hpRate();
    const mpRate = actor.mpRate();
    // background
    bars.bitmap.fillRect(0, 0, w, h, "rgba(0,0,0,0.5)");
    bars.bitmap.fillRect(0, h + 6, w, h, "rgba(0,0,0,0.5)");
    // hp bar
    bars.bitmap.fillRect(0, 0, Math.floor(w * hpRate), h, OP_CONFIG.colors[0]); // use leader color? keep gold for visibility
    // mp bar
    bars.bitmap.fillRect(0, h + 6, Math.floor(w * mpRate), h, "#00AEEF");
    bars.opacity = this.opacity;
  };

  TF_HUD.prototype.updateMinis = function(minis) {
    for (let i = 0; i < OP_CONFIG.maxMiniToShow; i++) {
      const slot = this._sprites.minis[i];
      if (!slot) continue;
      const actor = minis[i];
      if (actor) {
        const partyIndex = $gameParty.members().indexOf(actor);
        const color = OP_CONFIG.colors[partyIndex] || OP_CONFIG.colors[(i + 1) % OP_CONFIG.colors.length];
        const rgb = hexToRgb(color);
        slot.sil.bitmap = ImageManager.loadPicture(OP_CONFIG.silhouetteImage);
        slot.sil.setBlendColor([rgb[0], rgb[1], rgb[2], 160]);
        slot.sil.opacity = this.opacity;
        // portrait if face exists
        const faceName = actor.faceName();
        if (faceName) {
          slot.portrait.bitmap = ImageManager.loadFace(faceName);
          slot.portrait.scale.x = (OP_CONFIG.miniSize / 144) * 2;
          slot.portrait.scale.y = (OP_CONFIG.miniSize / 144) * 2;
          slot.portrait.opacity = this.opacity;
        } else {
          slot.portrait.bitmap = null;
          slot.portrait.opacity = this.opacity;
        }
        slot.frame.opacity = this.opacity;
      } else {
        slot.sil.opacity = 0;
        slot.portrait.opacity = 0;
        slot.frame.opacity = 0;
      }
    }
  };

  // -------------------------
  // React to party changes (leader swap)
  // -------------------------
  const _Game_Party_swapOrder = Game_Party.prototype.swapOrder;
  Game_Party.prototype.swapOrder = function(index1, index2) {
    _Game_Party_swapOrder.call(this, index1, index2);
    // force HUD refresh
    const scene = SceneManager._scene;
    if (scene && scene._tfHud) {
      scene._tfHud.updateContent();
    }
  };

  // Also refresh when party members change (add/remove)
  const _Game_Party_addActor = Game_Party.prototype.addActor;
  Game_Party.prototype.addActor = function(actorId) {
    _Game_Party_addActor.call(this, actorId);
    const scene = SceneManager._scene;
    if (scene && scene._tfHud) scene._tfHud.updateContent();
  };

  const _Game_Party_removeActor = Game_Party.prototype.removeActor;
  Game_Party.prototype.removeActor = function(actorId) {
    _Game_Party_removeActor.call(this, actorId);
    const scene = SceneManager._scene;
    if (scene && scene._tfHud) scene._tfHud.updateContent();
  };

  // -------------------------
  // Clean up on scene end
  // -------------------------
  const _Scene_Map_terminate = Scene_Map.prototype.terminate;
  Scene_Map.prototype.terminate = function() {
    _Scene_Map_terminate.call(this);
    if (this._tfHud) {
      this.removeChild(this._tfHud);
      this._tfHud = null;
    }
  };

})();

Embed on website

To embed this project on your website, copy the following code and paste it into your website's HTML: