440 lines
17 KiB
JavaScript
440 lines
17 KiB
JavaScript
//=============================================================================
|
|
// MinimapZoom.js
|
|
//=============================================================================
|
|
|
|
/*:
|
|
* @plugindesc Adds a minimap/zoom-out feature. Press O to toggle a zoomed-out view of the entire map.
|
|
* @author Dazed Translations
|
|
*
|
|
* @param Toggle Key
|
|
* @desc The key to toggle minimap (keycode: O = 79)
|
|
* @default 79
|
|
*
|
|
* @help
|
|
* Press the O key while on the map to toggle a zoomed-out minimap view.
|
|
* Shows the entire map scaled to fit the screen.
|
|
* Character sprites are hidden, and a marker shows your position.
|
|
* Press O again to return to normal view.
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Plugin parameters
|
|
const pluginName = 'MinimapZoom';
|
|
const parameters = PluginManager.parameters(pluginName);
|
|
const toggleKeyCode = Number(parameters['Toggle Key'] || 79); // O key
|
|
|
|
// Track minimap state
|
|
let minimapActive = false;
|
|
|
|
//=========================================================================
|
|
// Input - Add minimap key
|
|
//=========================================================================
|
|
|
|
Input.keyMapper[toggleKeyCode] = 'minimap';
|
|
|
|
//=========================================================================
|
|
// Scene_Map - Handle minimap toggle
|
|
//=========================================================================
|
|
|
|
const _Scene_Map_update = Scene_Map.prototype.update;
|
|
Scene_Map.prototype.update = function() {
|
|
_Scene_Map_update.call(this);
|
|
this.updateMinimapToggle();
|
|
};
|
|
|
|
Scene_Map.prototype.updateMinimapToggle = function() {
|
|
// Only allow toggle when not busy with messages/menus
|
|
if (Input.isTriggered('minimap') && !$gameMessage.isBusy()) {
|
|
this.toggleMinimap();
|
|
}
|
|
|
|
// Update player marker position if minimap is active
|
|
if (minimapActive && this._minimapPlayerMarker) {
|
|
this.updateMinimapPlayerMarker();
|
|
}
|
|
};
|
|
|
|
Scene_Map.prototype.toggleMinimap = function() {
|
|
minimapActive = !minimapActive;
|
|
|
|
if (minimapActive) {
|
|
this.showMinimap();
|
|
SoundManager.playCursor();
|
|
} else {
|
|
this.hideMinimap();
|
|
SoundManager.playCancel();
|
|
}
|
|
};
|
|
|
|
Scene_Map.prototype.showMinimap = function() {
|
|
if (!this._spriteset) return;
|
|
|
|
// Hide the normal spriteset
|
|
this._spriteset.visible = false;
|
|
|
|
// Create minimap overlay
|
|
this.createMinimapOverlay();
|
|
};
|
|
|
|
Scene_Map.prototype.hideMinimap = function() {
|
|
if (!this._spriteset) return;
|
|
|
|
// Show the normal spriteset
|
|
this._spriteset.visible = true;
|
|
|
|
// Remove minimap overlay
|
|
this.removeMinimapOverlay();
|
|
};
|
|
|
|
Scene_Map.prototype.createMinimapOverlay = function() {
|
|
if (this._minimapOverlay) return;
|
|
|
|
// Create container for minimap
|
|
this._minimapOverlay = new Sprite();
|
|
this._minimapOverlay.bitmap = new Bitmap(Graphics.width, Graphics.height);
|
|
|
|
// Fill background
|
|
this._minimapOverlay.bitmap.fillRect(0, 0, Graphics.width, Graphics.height, '#000000');
|
|
|
|
// Get map dimensions
|
|
const mapWidth = $gameMap.width();
|
|
const mapHeight = $gameMap.height();
|
|
const tileWidth = $gameMap.tileWidth();
|
|
const tileHeight = $gameMap.tileHeight();
|
|
|
|
// Calculate scale to fit entire map on screen with padding
|
|
const padding = 60;
|
|
const availableWidth = Graphics.width - padding * 2;
|
|
const availableHeight = Graphics.height - padding * 2;
|
|
|
|
const mapPixelWidth = mapWidth * tileWidth;
|
|
const mapPixelHeight = mapHeight * tileHeight;
|
|
|
|
const scaleX = availableWidth / mapPixelWidth;
|
|
const scaleY = availableHeight / mapPixelHeight;
|
|
const scale = Math.min(scaleX, scaleY, 1); // Don't zoom in past 1x
|
|
|
|
const scaledWidth = mapPixelWidth * scale;
|
|
const scaledHeight = mapPixelHeight * scale;
|
|
|
|
// Center the map
|
|
const offsetX = (Graphics.width - scaledWidth) / 2;
|
|
const offsetY = (Graphics.height - scaledHeight) / 2;
|
|
|
|
// Store for marker positioning
|
|
this._minimapScale = scale;
|
|
this._minimapOffsetX = offsetX;
|
|
this._minimapOffsetY = offsetY;
|
|
this._minimapTileWidth = tileWidth;
|
|
this._minimapTileHeight = tileHeight;
|
|
|
|
// Create the map snapshot sprite
|
|
this._minimapContent = new Sprite();
|
|
this._minimapContent.x = offsetX;
|
|
this._minimapContent.y = offsetY;
|
|
this._minimapContent.scale.x = scale;
|
|
this._minimapContent.scale.y = scale;
|
|
|
|
// Render the tilemap to a bitmap
|
|
this.renderMinimapTiles();
|
|
|
|
// Draw border around map
|
|
const ctx = this._minimapOverlay.bitmap.context;
|
|
ctx.strokeStyle = '#FFFFFF';
|
|
ctx.lineWidth = 2;
|
|
ctx.strokeRect(offsetX - 2, offsetY - 2, scaledWidth + 4, scaledHeight + 4);
|
|
|
|
// Add title text
|
|
this._minimapOverlay.bitmap.fontSize = 24;
|
|
this._minimapOverlay.bitmap.textColor = '#FFFFFF';
|
|
this._minimapOverlay.bitmap.outlineColor = '#000000';
|
|
this._minimapOverlay.bitmap.outlineWidth = 4;
|
|
this._minimapOverlay.bitmap.drawText('MAP VIEW - Press O to close', 0, 10, Graphics.width, 30, 'center');
|
|
|
|
// Add map name
|
|
const mapName = $gameMap.displayName() || ('Map ' + $gameMap.mapId());
|
|
this._minimapOverlay.bitmap.fontSize = 18;
|
|
this._minimapOverlay.bitmap.drawText(mapName, 0, Graphics.height - 35, Graphics.width, 24, 'center');
|
|
|
|
this.addChild(this._minimapOverlay);
|
|
this.addChild(this._minimapContent);
|
|
|
|
// Create player marker
|
|
this.createMinimapPlayerMarker();
|
|
};
|
|
|
|
Scene_Map.prototype.renderMinimapTiles = function() {
|
|
const mapWidth = $gameMap.width();
|
|
const mapHeight = $gameMap.height();
|
|
const tileWidth = $gameMap.tileWidth();
|
|
const tileHeight = $gameMap.tileHeight();
|
|
|
|
// Create bitmap for the map
|
|
const bitmap = new Bitmap(mapWidth * tileWidth, mapHeight * tileHeight);
|
|
this._minimapContent.bitmap = bitmap;
|
|
|
|
// Get tileset
|
|
const tileset = $gameMap.tileset();
|
|
if (!tileset) return;
|
|
|
|
// Simple tile rendering - draw colored rectangles based on passability
|
|
for (let y = 0; y < mapHeight; y++) {
|
|
for (let x = 0; x < mapWidth; x++) {
|
|
const px = x * tileWidth;
|
|
const py = y * tileHeight;
|
|
|
|
// Check tile properties
|
|
const isPassable = $gameMap.isPassable(x, y, 2) ||
|
|
$gameMap.isPassable(x, y, 4) ||
|
|
$gameMap.isPassable(x, y, 6) ||
|
|
$gameMap.isPassable(x, y, 8);
|
|
|
|
// Get terrain tag for variety
|
|
const terrainTag = $gameMap.terrainTag(x, y);
|
|
const regionId = $gameMap.regionId(x, y);
|
|
|
|
// Color based on passability and terrain
|
|
let color;
|
|
if (!isPassable) {
|
|
// Impassable - walls, obstacles
|
|
color = '#4a4a4a';
|
|
} else if (terrainTag > 0) {
|
|
// Special terrain
|
|
const terrainColors = ['#5b8a5b', '#6b9b6b', '#4a7a7a', '#7a6a5a', '#5a6a7a', '#7a5a6a', '#6a7a5a'];
|
|
color = terrainColors[(terrainTag - 1) % terrainColors.length];
|
|
} else {
|
|
// Normal passable floor
|
|
color = '#7a9a7a';
|
|
}
|
|
|
|
// Add some variation based on position
|
|
if ((x + y) % 2 === 0) {
|
|
color = this.adjustColor(color, 10);
|
|
}
|
|
|
|
bitmap.fillRect(px, py, tileWidth, tileHeight, color);
|
|
|
|
// Draw subtle grid
|
|
bitmap.fillRect(px, py, tileWidth, 1, 'rgba(0,0,0,0.1)');
|
|
bitmap.fillRect(px, py, 1, tileHeight, 'rgba(0,0,0,0.1)');
|
|
}
|
|
}
|
|
|
|
// Draw important events as markers (NPCs only, not decorations)
|
|
for (const event of $gameMap.events()) {
|
|
if (event && this.isImportantEvent(event)) {
|
|
const ex = event.x * tileWidth + tileWidth / 2;
|
|
const ey = event.y * tileHeight + tileHeight / 2;
|
|
|
|
// Draw event marker
|
|
const ctx = bitmap.context;
|
|
ctx.fillStyle = '#FFD700';
|
|
ctx.beginPath();
|
|
ctx.arc(ex, ey, Math.min(tileWidth, tileHeight) / 3, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.strokeStyle = '#000000';
|
|
ctx.lineWidth = 1;
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
};
|
|
|
|
// Determine if an event is "important" (NPC, not decoration/ambient)
|
|
Scene_Map.prototype.isImportantEvent = function(event) {
|
|
if (!event) return false;
|
|
|
|
// Must have a character graphic
|
|
if (!event.characterName()) return false;
|
|
|
|
// Get the event data
|
|
const eventData = event.event();
|
|
if (!eventData) return false;
|
|
|
|
// Check the current page
|
|
const page = event.page();
|
|
if (!page) return false;
|
|
|
|
// Skip if no trigger (parallel process/autorun without interaction)
|
|
// Trigger types: 0=Action Button, 1=Player Touch, 2=Event Touch, 3=Autorun, 4=Parallel
|
|
const trigger = page.trigger;
|
|
|
|
// Only show Action Button (0) and Player Touch (1) - these are interactive NPCs
|
|
if (trigger !== 0 && trigger !== 1) return false;
|
|
|
|
// Skip common decoration/ambient event names
|
|
const name = eventData.name.toLowerCase();
|
|
const skipPatterns = [
|
|
'窓', 'window', // Windows
|
|
'鳩', 'bird', 'pigeon', // Birds
|
|
'猫', 'cat', // Cats
|
|
'犬', 'dog', // Dogs
|
|
'light', '灯', 'lamp', // Lights
|
|
'flame', '炎', 'fire', // Fire effects
|
|
'smoke', '煙', // Smoke
|
|
'effect', '効果', // Effects
|
|
'particle', // Particles
|
|
'ambient', // Ambient
|
|
'decor', '装飾', // Decorations
|
|
'bg', 'background', // Background elements
|
|
'fog', '霧', // Fog
|
|
'water', '水', // Water effects
|
|
'shadow', '影', // Shadows
|
|
'grass', '草', // Grass
|
|
'tree', '木', // Trees
|
|
'flower', '花', // Flowers
|
|
'rock', '岩', // Rocks
|
|
'sparkle', 'shine', // Sparkle effects
|
|
'animation', 'anim', // Animations
|
|
];
|
|
|
|
for (const pattern of skipPatterns) {
|
|
if (name.includes(pattern)) return false;
|
|
}
|
|
|
|
// Skip events with very short command lists (likely just empty or simple triggers)
|
|
const list = page.list;
|
|
if (!list || list.length <= 2) return false;
|
|
|
|
// Check if event has meaningful commands (text, choices, transfers, etc.)
|
|
// Command codes: 101=Show Text, 102=Show Choices, 201=Transfer Player,
|
|
// 121=Control Switches, 122=Control Variables, 301=Battle Processing
|
|
const meaningfulCodes = [101, 102, 103, 104, 105, 201, 301, 302, 303, 311, 312, 326];
|
|
let hasMeaningfulCommand = false;
|
|
|
|
for (const cmd of list) {
|
|
if (meaningfulCodes.includes(cmd.code)) {
|
|
hasMeaningfulCommand = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return hasMeaningfulCommand;
|
|
};
|
|
|
|
Scene_Map.prototype.adjustColor = function(hex, amount) {
|
|
// Simple color adjustment
|
|
let r = parseInt(hex.slice(1, 3), 16);
|
|
let g = parseInt(hex.slice(3, 5), 16);
|
|
let b = parseInt(hex.slice(5, 7), 16);
|
|
|
|
r = Math.min(255, Math.max(0, r + amount));
|
|
g = Math.min(255, Math.max(0, g + amount));
|
|
b = Math.min(255, Math.max(0, b + amount));
|
|
|
|
return '#' + r.toString(16).padStart(2, '0') +
|
|
g.toString(16).padStart(2, '0') +
|
|
b.toString(16).padStart(2, '0');
|
|
};
|
|
|
|
Scene_Map.prototype.createMinimapPlayerMarker = function() {
|
|
if (this._minimapPlayerMarker) return;
|
|
|
|
// Create player marker sprite
|
|
this._minimapPlayerMarker = new Sprite();
|
|
this._minimapPlayerMarker.bitmap = new Bitmap(32, 32);
|
|
|
|
// Draw a bright marker
|
|
const ctx = this._minimapPlayerMarker.bitmap.context;
|
|
|
|
// Outer glow
|
|
ctx.fillStyle = '#FF0000';
|
|
ctx.beginPath();
|
|
ctx.arc(16, 16, 12, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Inner bright
|
|
ctx.fillStyle = '#FFFF00';
|
|
ctx.beginPath();
|
|
ctx.arc(16, 16, 8, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Center dot
|
|
ctx.fillStyle = '#FFFFFF';
|
|
ctx.beginPath();
|
|
ctx.arc(16, 16, 4, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
this._minimapPlayerMarker.anchor.x = 0.5;
|
|
this._minimapPlayerMarker.anchor.y = 0.5;
|
|
|
|
this.addChild(this._minimapPlayerMarker);
|
|
this.updateMinimapPlayerMarker();
|
|
};
|
|
|
|
Scene_Map.prototype.updateMinimapPlayerMarker = function() {
|
|
if (!this._minimapPlayerMarker) return;
|
|
|
|
const playerX = $gamePlayer.x;
|
|
const playerY = $gamePlayer.y;
|
|
|
|
// Calculate marker position
|
|
const markerX = this._minimapOffsetX + (playerX + 0.5) * this._minimapTileWidth * this._minimapScale;
|
|
const markerY = this._minimapOffsetY + (playerY + 0.5) * this._minimapTileHeight * this._minimapScale;
|
|
|
|
this._minimapPlayerMarker.x = markerX;
|
|
this._minimapPlayerMarker.y = markerY;
|
|
|
|
// Pulse effect
|
|
const pulse = Math.sin(Graphics.frameCount * 0.1) * 0.2 + 1;
|
|
this._minimapPlayerMarker.scale.x = pulse;
|
|
this._minimapPlayerMarker.scale.y = pulse;
|
|
};
|
|
|
|
Scene_Map.prototype.removeMinimapOverlay = function() {
|
|
if (this._minimapOverlay) {
|
|
this.removeChild(this._minimapOverlay);
|
|
this._minimapOverlay = null;
|
|
}
|
|
if (this._minimapContent) {
|
|
this.removeChild(this._minimapContent);
|
|
this._minimapContent = null;
|
|
}
|
|
if (this._minimapPlayerMarker) {
|
|
this.removeChild(this._minimapPlayerMarker);
|
|
this._minimapPlayerMarker = null;
|
|
}
|
|
};
|
|
|
|
//=========================================================================
|
|
// Handle scene transitions - Reset minimap when leaving map
|
|
//=========================================================================
|
|
|
|
const _Scene_Map_terminate = Scene_Map.prototype.terminate;
|
|
Scene_Map.prototype.terminate = function() {
|
|
if (minimapActive) {
|
|
this.hideMinimap();
|
|
minimapActive = false;
|
|
}
|
|
_Scene_Map_terminate.call(this);
|
|
};
|
|
|
|
//=========================================================================
|
|
// Prevent menu/actions while minimap is active
|
|
//=========================================================================
|
|
|
|
const _Scene_Map_isMenuEnabled = Scene_Map.prototype.isMenuEnabled;
|
|
Scene_Map.prototype.isMenuEnabled = function() {
|
|
if (minimapActive) return false;
|
|
return _Scene_Map_isMenuEnabled.call(this);
|
|
};
|
|
|
|
const _Scene_Map_isMapTouchOk = Scene_Map.prototype.isMapTouchOk;
|
|
Scene_Map.prototype.isMapTouchOk = function() {
|
|
if (minimapActive) return false;
|
|
return _Scene_Map_isMapTouchOk.call(this);
|
|
};
|
|
|
|
const _Game_Player_canMove = Game_Player.prototype.canMove;
|
|
Game_Player.prototype.canMove = function() {
|
|
if (minimapActive) return false;
|
|
return _Game_Player_canMove.call(this);
|
|
};
|
|
|
|
// Public accessor for minimap state
|
|
Scene_Map.prototype.isMinimapActive = function() {
|
|
return minimapActive;
|
|
};
|
|
|
|
})();
|