RPG-Vault Minimap

RPG Maker MZ (JavaScript)
Autor
GR-FoxDie
Sprache
JavaScript
Zeilen
623
Eingestellt von
GR-FoxDie
Aufrufe
7
Im Forum
[script=20]

Dieses Plugin zeigt in einer Ecke der Kartenansicht eine Minimap an. Begehbare Felder, der Spieler, Events und Ausgänge werden farbig dargestellt. Ist die Karte größer als die Minimap, scrollt sie automatisch mit dem Spieler mit.
Funktionen

Anzeige von begehbaren Feldern, Spieler, Events und Ausgängen (Events mit "Spieler transferieren")
Automatisches Mitscrollen bei großen Karten
Sanftes Ein- und Ausblenden
Position frei wählbar (oben/unten, links/rechts) inklusive Abstand
Größe, Feldgröße, Punktgröße und Deckkraft einstellbar
Alle Farben anpassbar (Boden, Wand, Hintergrund, Rahmen, Spieler, Events, Ausgänge)
Optionaler Schalter: Minimap nur anzeigen, wenn ein bestimmter Schalter AN ist
Optionale Taste zum Ein- und Ausblenden durch den Spieler (Standard: M)
Minimap wird bei Nachrichten automatisch ausgeblendet (abschaltbar)
Transparente Darstellung: Deckkraft von Hintergrund, Rahmen und Kartenfläche getrennt einstellbar
Darstellung der Original-Karte (verkleinerte Tileset-Grafik) oder wahlweise als einfarbiges Schema

Als .js-Datei
//=============================================================================
// RPGVault_Minimap.js
//=============================================================================

/*:
 * @target MZ
 * @plugindesc v1.2.0 Zeigt eine Minimap mit Spieler, Events und Ausgängen auf der Karte.
 * @author GR-FoxDie
 * @url https://rpg-vault.de
 *
 * @help
 * ============================================================================
 * RPGVault_Minimap
 * ============================================================================
 * Zeigt in einer Ecke der Kartenansicht eine Minimap an. Begehbare Felder,
 * der Spieler, Events und Ausgänge (Events mit "Transfer Player") werden
 * farbig dargestellt. Ist die Karte größer als die Minimap, scrollt die
 * Minimap automatisch mit dem Spieler mit.
 *
 * Die Karte kann als verkleinerte ORIGINAL-Grafik (Tileset der Karte) oder
 * als einfarbiges Schema (Boden/Wand) dargestellt werden. Bei der Original-
 * Darstellung werden die Tile-Ebenen der Karte (ohne Schatten, Regionen und
 * Parallax-Hintergrund) mit Feldgröße 6-8 Pixel am besten lesbar.
 *
 * Dateiname: RPGVault_Minimap.js (nicht umbenennen!)
 *
 * ----------------------------------------------------------------------------
 * NOTETAGS
 * ----------------------------------------------------------------------------
 * Karten-Notiz:
 *   <NoMinimap>      Auf dieser Karte wird keine Minimap angezeigt.
 *
 * Event-Notiz:
 *   <MinimapHide>    Dieses Event wird auf der Minimap nicht angezeigt.
 *
 * ----------------------------------------------------------------------------
 * PLUGIN-BEFEHLE
 * ----------------------------------------------------------------------------
 * Minimap anzeigen / Minimap verbergen / Minimap umschalten /
 * Minimap neu zeichnen (z.B. nach Änderung der Karte per Script).
 *
 * ----------------------------------------------------------------------------
 * TASTE
 * ----------------------------------------------------------------------------
 * Optional kann ein Buchstabe (A-Z) festgelegt werden, mit dem der Spieler
 * die Minimap ein- und ausblenden kann (Standard: M).
 *
 * ----------------------------------------------------------------------------
 * LIZENZ / LICENSE
 * ----------------------------------------------------------------------------
 * - Erlaubt: Nutzung in NICHT-kommerziellen UND kommerziellen Projekten.
 * - Erlaubt: Anpassungen für das eigene Projekt.
 * - VERBOTEN: Repost, Weitergabe oder Upload dieser Datei (auch verändert)
 *   auf anderen Seiten, Foren, Cloud-Diensten oder in Asset-Paketen.
 * - VERBOTEN: Entfernen oder Verändern der Header-Hinweise, Copyright-
 *   Angaben und Download-Verweise im Code (auch bei angepassten Versionen).
 * - Einziger erlaubter Download-Ort: https://rpg-vault.de
 * - Credits: erwünscht, aber nicht verpflichtend.
 * - Nutzung auf eigene Gefahr, keine Garantie.
 *
 * EN: Free for non-commercial and commercial use. Do NOT repost or
 * redistribute. Header notices, copyright statements and download
 * references in the code must NOT be removed or altered, even in
 * modified versions. Download is only allowed at https://rpg-vault.de
 *
 * @command show
 * @text Minimap anzeigen
 * @desc Blendet die Minimap ein.
 *
 * @command hide
 * @text Minimap verbergen
 * @desc Blendet die Minimap aus.
 *
 * @command toggle
 * @text Minimap umschalten
 * @desc Schaltet die Minimap zwischen sichtbar und verborgen um.
 *
 * @command refresh
 * @text Minimap neu zeichnen
 * @desc Zeichnet die Minimap der aktuellen Karte komplett neu.
 *
 * @param position
 * @text Position
 * @type select
 * @option Oben rechts
 * @value topRight
 * @option Oben links
 * @value topLeft
 * @option Unten rechts
 * @value bottomRight
 * @option Unten links
 * @value bottomLeft
 * @default topRight
 *
 * @param marginX
 * @text Abstand horizontal
 * @type number
 * @min 0
 * @default 8
 *
 * @param marginY
 * @text Abstand vertikal
 * @type number
 * @min 0
 * @default 8
 *
 * @param width
 * @text Breite (Pixel)
 * @type number
 * @min 40
 * @default 180
 *
 * @param height
 * @text Höhe (Pixel)
 * @type number
 * @min 40
 * @default 140
 *
 * @param mapMode
 * @text Kartendarstellung
 * @desc Original = echte Karten-Grafik (Tileset) verkleinert. Schema = einfarbig (Boden/Wand).
 * @type select
 * @option Original-Karte (Tileset-Grafik)
 * @value original
 * @option Schema (Boden/Wand einfarbig)
 * @value schema
 * @default original
 *
 * @param tileSize
 * @text Feldgröße (Pixel)
 * @desc Größe eines Karten-Feldes auf der Minimap (1-16). Für die Original-Karte sind 6-8 empfehlenswert.
 * @type number
 * @min 1
 * @max 16
 * @default 6
 *
 * @param dotSize
 * @text Punktgröße (Pixel)
 * @desc Größe der Punkte für Spieler, Events und Ausgänge.
 * @type number
 * @min 1
 * @max 16
 * @default 4
 *
 * @param opacity
 * @text Deckkraft
 * @type number
 * @min 0
 * @max 255
 * @default 210
 *
 * @param backgroundOpacity
 * @text Deckkraft: Hintergrund + Rahmen
 * @desc 0 = unsichtbar, 255 = voll deckend. Spieler/Events bleiben davon unberührt.
 * @type number
 * @min 0
 * @max 255
 * @default 255
 *
 * @param mapOpacity
 * @text Deckkraft: Kartenfläche
 * @desc Deckkraft der Boden-/Wandfarben. Niedrige Werte = Spielkarte scheint durch.
 * @type number
 * @min 0
 * @max 255
 * @default 255
 *
 * @param switchId
 * @text Schalter (optional)
 * @desc Minimap wird nur angezeigt, wenn dieser Schalter AN ist. 0 = immer.
 * @type switch
 * @default 0
 *
 * @param toggleKey
 * @text Umschalt-Taste
 * @desc Ein Buchstabe A-Z. Leer lassen = keine Taste.
 * @type string
 * @default M
 *
 * @param showEvents
 * @text Events anzeigen
 * @type boolean
 * @default true
 *
 * @param showTransfers
 * @text Ausgänge anzeigen
 * @desc Events mit "Transfer Player" in einer eigenen Farbe anzeigen.
 * @type boolean
 * @default true
 *
 * @param hideOnMessage
 * @text Bei Nachrichten ausblenden
 * @type boolean
 * @default true
 *
 * @param colorFloor
 * @text Farbe: Boden
 * @desc CSS-Farbe, z.B. #8fa3b8 oder rgba(255,255,255,0.6)
 * @type string
 * @default #8fa3b8
 *
 * @param colorWall
 * @text Farbe: Wand / Unbegehbar
 * @type string
 * @default #2b2f3a
 *
 * @param colorBackground
 * @text Farbe: Hintergrund
 * @type string
 * @default #15171d
 *
 * @param colorBorder
 * @text Farbe: Rahmen
 * @type string
 * @default #e8e8e8
 *
 * @param colorPlayer
 * @text Farbe: Spieler
 * @type string
 * @default #ffd400
 *
 * @param colorEvent
 * @text Farbe: Events
 * @type string
 * @default #ff5a5a
 *
 * @param colorTransfer
 * @text Farbe: Ausgänge
 * @type string
 * @default #4cd964
 */

(() => {
    "use strict";

    const PLUGIN_NAME = "RPGVault_Minimap";
    const params = PluginManager.parameters(PLUGIN_NAME);

    const clamp = (v, min, max) => Math.max(min, Math.min(max, v));

    const CFG = {
        position: String(params.position || "topRight"),
        marginX: Number(params.marginX || 8),
        marginY: Number(params.marginY || 8),
        width: Number(params.width || 180),
        height: Number(params.height || 140),
        mapMode: String(params.mapMode || "original"),
        tileSize: clamp(Number(params.tileSize || 6), 1, 16),
        dotSize: clamp(Number(params.dotSize || 4), 1, 16),
        opacity: clamp(Number(params.opacity || 210), 0, 255),
        backgroundOpacity: clamp(Number(params.backgroundOpacity === undefined || params.backgroundOpacity === "" ? 255 : params.backgroundOpacity), 0, 255),
        mapOpacity: clamp(Number(params.mapOpacity === undefined || params.mapOpacity === "" ? 255 : params.mapOpacity), 0, 255),
        switchId: Number(params.switchId || 0),
        toggleKey: String(params.toggleKey || "").trim().toUpperCase().charAt(0),
        showEvents: params.showEvents !== "false",
        showTransfers: params.showTransfers !== "false",
        hideOnMessage: params.hideOnMessage !== "false",
        colorFloor: String(params.colorFloor || "#8fa3b8"),
        colorWall: String(params.colorWall || "#2b2f3a"),
        colorBackground: String(params.colorBackground || "#15171d"),
        colorBorder: String(params.colorBorder || "#e8e8e8"),
        colorPlayer: String(params.colorPlayer || "#ffd400"),
        colorEvent: String(params.colorEvent || "#ff5a5a"),
        colorTransfer: String(params.colorTransfer || "#4cd964")
    };

    const BORDER = 2;
    const FADE_SPEED = 16;

    // Taste registrieren
    if (CFG.toggleKey >= "A" && CFG.toggleKey <= "Z") {
        Input.keyMapper[CFG.toggleKey.charCodeAt(0)] = "rpgvaultMinimap";
    }

    //-------------------------------------------------------------------------
    // Plugin-Befehle
    //-------------------------------------------------------------------------

    PluginManager.registerCommand(PLUGIN_NAME, "show", () => {
        $gameSystem._minimapVisible = true;
    });

    PluginManager.registerCommand(PLUGIN_NAME, "hide", () => {
        $gameSystem._minimapVisible = false;
    });

    PluginManager.registerCommand(PLUGIN_NAME, "toggle", () => {
        $gameSystem._minimapVisible = $gameSystem._minimapVisible === false;
    });

    PluginManager.registerCommand(PLUGIN_NAME, "refresh", () => {
        const scene = SceneManager._scene;
        if (scene && scene._rpgvaultMinimap) {
            scene._rpgvaultMinimap.refresh();
        }
    });

    //-------------------------------------------------------------------------
    // Game_System
    //-------------------------------------------------------------------------

    const _Game_System_initialize = Game_System.prototype.initialize;
    Game_System.prototype.initialize = function() {
        _Game_System_initialize.call(this);
        this._minimapVisible = true;
    };

    //-------------------------------------------------------------------------
    // Sprite_Minimap
    //-------------------------------------------------------------------------

    class Sprite_Minimap extends Sprite {
        constructor() {
            super();
            this._frameX = -1;
            this._frameY = -1;
            this._transferCache = new WeakMap();
            this._background = new Sprite();
            this._mapSprite = new Sprite();
            this._dynSprite = new Sprite();
            this.addChild(this._background);
            this.addChild(this._mapSprite);
            this.addChild(this._dynSprite);
            this.opacity = 0;
            this.refresh();
        }

        isMapEnabled() {
            return !($dataMap && $dataMap.meta && $dataMap.meta.NoMinimap);
        }

        shouldShow() {
            if (!this.isMapEnabled()) return false;
            if ($gameSystem._minimapVisible === false) return false;
            if (CFG.switchId > 0 && !$gameSwitches.value(CFG.switchId)) return false;
            if (CFG.hideOnMessage && $gameMessage.isBusy()) return false;
            return true;
        }

        refresh() {
            const ts = CFG.tileSize;
            const mapW = $gameMap.width() * ts;
            const mapH = $gameMap.height() * ts;
            this._dispW = Math.min(CFG.width, mapW);
            this._dispH = Math.min(CFG.height, mapH);
            this._frameX = -1;
            this._frameY = -1;

            // Hintergrund + Rahmen
            const bw = this._dispW + BORDER * 2;
            const bh = this._dispH + BORDER * 2;
            const bg = new Bitmap(bw, bh);
            bg.fillRect(0, 0, bw, bh, CFG.colorBorder);
            bg.fillRect(BORDER, BORDER, this._dispW, this._dispH, CFG.colorBackground);
            this._background.bitmap = bg;
            this._background.opacity = CFG.backgroundOpacity;

            // Statische Karte
            if (CFG.mapMode === "original") {
                // Leere Fläche, bis die Tileset-Bilder geladen sind
                this._mapSprite.bitmap = new Bitmap(mapW, mapH);
                this._tilesetBitmaps = this.loadTilesetBitmaps();
                this._originalPending = true;
            } else {
                this._originalPending = false;
                this._mapSprite.bitmap = this.buildMapBitmap();
            }
            this._mapSprite.opacity = CFG.mapOpacity;
            this._mapSprite.x = BORDER;
            this._mapSprite.y = BORDER;

            // Dynamische Ebene (Spieler, Events)
            this._dynSprite.bitmap = new Bitmap(this._dispW, this._dispH);
            this._dynSprite.x = BORDER;
            this._dynSprite.y = BORDER;

            this.updatePosition(bw, bh);
        }

        buildMapBitmap() {
            const ts = CFG.tileSize;
            const w = $gameMap.width();
            const h = $gameMap.height();
            const bmp = new Bitmap(w * ts, h * ts);
            bmp.fillRect(0, 0, w * ts, h * ts, CFG.colorWall);
            const dirs = [2, 4, 6, 8];
            for (let y = 0; y < h; y++) {
                for (let x = 0; x < w; x++) {
                    if (dirs.some(d => $gameMap.isPassable(x, y, d))) {
                        bmp.fillRect(x * ts, y * ts, ts, ts, CFG.colorFloor);
                    }
                }
            }
            return bmp;
        }

        loadTilesetBitmaps() {
            const tileset = $gameMap.tileset();
            const names = tileset ? tileset.tilesetNames : [];
            return names.map(name => (name ? ImageManager.loadTileset(name) : null));
        }

        updateOriginal() {
            if (!this._originalPending) return;
            const ready = this._tilesetBitmaps.every(b => !b || b.isReady() || b.isError());
            if (!ready) return;
            this._originalPending = false;
            this._mapSprite.bitmap = this.buildOriginalBitmap();
            this._frameX = -1;
            this._frameY = -1;
        }

        buildOriginalBitmap() {
            const ts = CFG.tileSize;
            const w = $gameMap.width();
            const h = $gameMap.height();
            const tw = $gameMap.tileWidth();
            const th = $gameMap.tileHeight();
            const bmp = new Bitmap(w * ts, h * ts);
            const data = $dataMap.data;
            for (let y = 0; y < h; y++) {
                for (let x = 0; x < w; x++) {
                    for (let z = 0; z < 4; z++) {
                        const id = data[(z * h + y) * w + x] || 0;
                        if (id > 0 && id < Tilemap.TILE_ID_MAX) {
                            this.drawOriginalTile(bmp, id, x * ts, y * ts, ts, tw, th);
                        }
                    }
                }
            }
            return bmp;
        }

        drawOriginalTile(bmp, id, dx, dy, ts, tw, th) {
            if (Tilemap.isAutotile(id)) {
                this.drawOriginalAutotile(bmp, id, dx, dy, ts, tw, th);
                return;
            }
            const setNumber = Tilemap.isTileA5(id) ? 4 : 5 + Math.floor(id / 256);
            const src = this._tilesetBitmaps[setNumber];
            if (!src || !src.isReady()) return;
            const sx = ((Math.floor(id / 128) % 2) * 8 + (id % 8)) * tw;
            const sy = (Math.floor((id % 256) / 8) % 16) * th;
            bmp.blt(src, sx, sy, tw, th, dx, dy, ts, ts);
        }

        drawOriginalAutotile(bmp, id, dx, dy, ts, tw, th) {
            let table = Tilemap.FLOOR_AUTOTILE_TABLE;
            const kind = Tilemap.getAutotileKind(id);
            const shape = Tilemap.getAutotileShape(id);
            const tx = kind % 8;
            const ty = Math.floor(kind / 8);
            let bx = 0;
            let by = 0;
            let setNumber = 0;

            if (Tilemap.isTileA1(id)) {
                setNumber = 0;
                if (kind === 0) {
                    bx = 0; by = 0;
                } else if (kind === 1) {
                    bx = 0; by = 3;
                } else if (kind === 2) {
                    bx = 6; by = 0;
                } else if (kind === 3) {
                    bx = 6; by = 3;
                } else {
                    bx = Math.floor(tx / 4) * 8;
                    by = ty * 6 + (Math.floor(tx / 2) % 2) * 3;
                    if (kind % 2 === 1) {
                        bx += 6;
                        table = Tilemap.WATERFALL_AUTOTILE_TABLE;
                    }
                }
            } else if (Tilemap.isTileA2(id)) {
                setNumber = 1;
                bx = tx * 2;
                by = (ty - 2) * 3;
            } else if (Tilemap.isTileA3(id)) {
                setNumber = 2;
                bx = tx * 2;
                by = (ty - 6) * 2;
                table = Tilemap.WALL_AUTOTILE_TABLE;
            } else if (Tilemap.isTileA4(id)) {
                setNumber = 3;
                bx = tx * 2;
                by = Math.floor((ty - 10) * 2.5 + (ty % 2 === 1 ? 0.5 : 0));
                if (ty % 2 === 1) table = Tilemap.WALL_AUTOTILE_TABLE;
            }

            const src = this._tilesetBitmaps[setNumber];
            if (!src || !src.isReady()) return;
            const quarter = table[shape];
            if (!quarter) return;
            const qw = tw / 2;
            const qh = th / 2;
            const q = ts / 2;
            for (let i = 0; i < 4; i++) {
                const sx = (bx * 2 + quarter[i][0]) * qw;
                const sy = (by * 2 + quarter[i][1]) * qh;
                const ox = (i % 2) * q;
                const oy = Math.floor(i / 2) * q;
                bmp.blt(src, sx, sy, qw, qh, dx + ox, dy + oy, q, q);
            }
        }

        updatePosition(bw, bh) {
            const bx = Graphics.boxWidth;
            const by = Graphics.boxHeight;
            const left = CFG.position === "topLeft" || CFG.position === "bottomLeft";
            const top = CFG.position === "topLeft" || CFG.position === "topRight";
            this.x = left ? CFG.marginX : bx - bw - CFG.marginX;
            this.y = top ? CFG.marginY : by - bh - CFG.marginY;
        }

        update() {
            super.update();
            this.updateOriginal();
            this.updateFade();
            if (this.opacity > 0) {
                this.updateView();
                this.drawDynamic();
            }
        }

        updateFade() {
            if (this.shouldShow()) {
                this.opacity = Math.min(CFG.opacity, this.opacity + FADE_SPEED);
            } else {
                this.opacity = Math.max(0, this.opacity - FADE_SPEED);
            }
            this.visible = this.opacity > 0;
        }

        updateView() {
            const ts = CFG.tileSize;
            const mapW = $gameMap.width() * ts;
            const mapH = $gameMap.height() * ts;
            const px = ($gamePlayer._realX + 0.5) * ts;
            const py = ($gamePlayer._realY + 0.5) * ts;
            this._viewX = Math.round(clamp(px - this._dispW / 2, 0, Math.max(0, mapW - this._dispW)));
            this._viewY = Math.round(clamp(py - this._dispH / 2, 0, Math.max(0, mapH - this._dispH)));
            if (this._viewX !== this._frameX || this._viewY !== this._frameY) {
                this._mapSprite.setFrame(this._viewX, this._viewY, this._dispW, this._dispH);
                this._frameX = this._viewX;
                this._frameY = this._viewY;
            }
        }

        isTransferEvent(event) {
            const page = event.page();
            if (!page) return false;
            let result = this._transferCache.get(page.list);
            if (result === undefined) {
                result = page.list.some(cmd => cmd.code === 201);
                this._transferCache.set(page.list, result);
            }
            return result;
        }

        drawDot(bmp, realX, realY, color) {
            const ts = CFG.tileSize;
            const d = CFG.dotSize;
            const x = Math.round((realX + 0.5) * ts - this._viewX - d / 2);
            const y = Math.round((realY + 0.5) * ts - this._viewY - d / 2);
            if (x + d < 0 || y + d < 0 || x > this._dispW || y > this._dispH) return;
            bmp.fillRect(x, y, d, d, color);
        }

        drawDynamic() {
            const bmp = this._dynSprite.bitmap;
            bmp.clear();

            if (CFG.showEvents || CFG.showTransfers) {
                for (const event of $gameMap.events()) {
                    if (event._erased || !event.page()) continue;
                    const data = event.event();
                    if (data && data.meta && data.meta.MinimapHide) continue;

                    if (CFG.showTransfers && this.isTransferEvent(event)) {
                        this.drawDot(bmp, event._realX, event._realY, CFG.colorTransfer);
                        continue;
                    }
                    if (!CFG.showEvents) continue;
                    if (event.isTransparent()) continue;
                    if (!event.characterName() && event.tileId() <= 0) continue;
                    this.drawDot(bmp, event._realX, event._realY, CFG.colorEvent);
                }
            }

            if (!$gamePlayer.isTransparent()) {
                this.drawDot(bmp, $gamePlayer._realX, $gamePlayer._realY, CFG.colorPlayer);
            }
        }
    }

    //-------------------------------------------------------------------------
    // Scene_Map
    //-------------------------------------------------------------------------

    const _Scene_Map_createDisplayObjects = Scene_Map.prototype.createDisplayObjects;
    Scene_Map.prototype.createDisplayObjects = function() {
        _Scene_Map_createDisplayObjects.call(this);
        this.createRpgvaultMinimap();
    };

    Scene_Map.prototype.createRpgvaultMinimap = function() {
        this._rpgvaultMinimap = new Sprite_Minimap();
        // Unter dem Fensterlayer einfügen, damit Nachrichten darüber liegen
        const index = this.children.indexOf(this._windowLayer);
        this.addChildAt(this._rpgvaultMinimap, index >= 0 ? index : this.children.length);
    };

    const _Scene_Map_update = Scene_Map.prototype.update;
    Scene_Map.prototype.update = function() {
        _Scene_Map_update.call(this);
        if (CFG.toggleKey && Input.isTriggered("rpgvaultMinimap") &&
            !$gameMessage.isBusy() && !$gamePlayer.isMoveRouteForcing()) {
            $gameSystem._minimapVisible = $gameSystem._minimapVisible === false;
        }
    };

})();

Im Forum