860 lines
34 KiB
JavaScript
860 lines
34 KiB
JavaScript
//=============================================================================
|
|
// MinimapZoom.js
|
|
//=============================================================================
|
|
|
|
/*:
|
|
* @plugindesc Adds a minimap/zoom-out feature. Press M to toggle a zoomed-out view of the entire map.
|
|
* @author Dazed Translations
|
|
*
|
|
* @param Toggle Key
|
|
* @desc The key to toggle minimap (keycode: M = 79)
|
|
* @default 79
|
|
*
|
|
* @help
|
|
* Press the M 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 M 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); // M 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 and atmospheric effects if minimap is active
|
|
if (minimapActive) {
|
|
if (this._minimapPlayerMarker) {
|
|
this.updateMinimapPlayerMarker();
|
|
}
|
|
this.updateMinimapAtmosphere();
|
|
}
|
|
};
|
|
|
|
Scene_Map.prototype.updateMinimapAtmosphere = function() {
|
|
// Subtle flicker effect on the map content to simulate candlelight
|
|
if (this._minimapContent) {
|
|
const flicker = 0.92 + Math.sin(Graphics.frameCount * 0.05) * 0.03 +
|
|
Math.sin(Graphics.frameCount * 0.13) * 0.02;
|
|
this._minimapContent.opacity = Math.floor(255 * flicker);
|
|
}
|
|
};
|
|
|
|
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);
|
|
|
|
// Draw dark atmospheric background with vignette
|
|
this.drawAtmosphericBackground();
|
|
|
|
// 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 = 70;
|
|
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 ornate border around map
|
|
this.drawOrnateBorder(offsetX, offsetY, scaledWidth, scaledHeight);
|
|
|
|
// Add title text with gothic styling
|
|
this._minimapOverlay.bitmap.fontFace = 'GameFont';
|
|
this._minimapOverlay.bitmap.fontSize = 26;
|
|
this._minimapOverlay.bitmap.textColor = '#d4af37'; // Gold color
|
|
this._minimapOverlay.bitmap.outlineColor = '#1a0a00';
|
|
this._minimapOverlay.bitmap.outlineWidth = 4;
|
|
this._minimapOverlay.bitmap.drawText('~ Cartograph ~', 0, 12, Graphics.width, 30, 'center');
|
|
|
|
// Add map name with aged look
|
|
const mapName = $gameMap.displayName() || ('Unknown Region');
|
|
this._minimapOverlay.bitmap.fontSize = 20;
|
|
this._minimapOverlay.bitmap.textColor = '#c9a959';
|
|
this._minimapOverlay.bitmap.drawText('― ' + mapName + ' ―', 0, Graphics.height - 38, Graphics.width, 24, 'center');
|
|
|
|
// Smaller instruction text
|
|
this._minimapOverlay.bitmap.fontSize = 14;
|
|
this._minimapOverlay.bitmap.textColor = '#8b7355';
|
|
this._minimapOverlay.bitmap.drawText('Press M to close', 0, Graphics.height - 18, Graphics.width, 20, 'center');
|
|
|
|
// Draw legend
|
|
this.drawMinimapLegend();
|
|
|
|
this.addChild(this._minimapOverlay);
|
|
this.addChild(this._minimapContent);
|
|
|
|
// Create player marker
|
|
this.createMinimapPlayerMarker();
|
|
};
|
|
|
|
Scene_Map.prototype.drawAtmosphericBackground = function() {
|
|
const bitmap = this._minimapOverlay.bitmap;
|
|
const ctx = bitmap.context;
|
|
const w = Graphics.width;
|
|
const h = Graphics.height;
|
|
|
|
// Base dark parchment color
|
|
ctx.fillStyle = '#1a1410';
|
|
ctx.fillRect(0, 0, w, h);
|
|
|
|
// Add subtle parchment texture noise
|
|
for (let i = 0; i < 8000; i++) {
|
|
const x = Math.random() * w;
|
|
const y = Math.random() * h;
|
|
const alpha = Math.random() * 0.08;
|
|
const shade = Math.floor(Math.random() * 30) + 20;
|
|
ctx.fillStyle = `rgba(${shade}, ${shade - 5}, ${shade - 10}, ${alpha})`;
|
|
ctx.fillRect(x, y, 1, 1);
|
|
}
|
|
|
|
// Add vignette effect (darker edges)
|
|
const gradient = ctx.createRadialGradient(w/2, h/2, h * 0.3, w/2, h/2, h * 0.8);
|
|
gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
|
|
gradient.addColorStop(0.7, 'rgba(0, 0, 0, 0.3)');
|
|
gradient.addColorStop(1, 'rgba(0, 0, 0, 0.7)');
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(0, 0, w, h);
|
|
};
|
|
|
|
Scene_Map.prototype.drawOrnateBorder = function(x, y, width, height) {
|
|
const ctx = this._minimapOverlay.bitmap.context;
|
|
const borderSize = 6;
|
|
|
|
// Outer dark border
|
|
ctx.strokeStyle = '#0d0805';
|
|
ctx.lineWidth = borderSize + 4;
|
|
ctx.strokeRect(x - borderSize - 2, y - borderSize - 2, width + (borderSize + 2) * 2, height + (borderSize + 2) * 2);
|
|
|
|
// Main gold border
|
|
ctx.strokeStyle = '#8b7355';
|
|
ctx.lineWidth = borderSize;
|
|
ctx.strokeRect(x - borderSize/2, y - borderSize/2, width + borderSize, height + borderSize);
|
|
|
|
// Inner highlight
|
|
ctx.strokeStyle = '#c9a959';
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeRect(x - 1, y - 1, width + 2, height + 2);
|
|
|
|
// Corner ornaments
|
|
const cornerSize = 12;
|
|
ctx.fillStyle = '#d4af37';
|
|
|
|
// Top-left corner
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, cornerSize/2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Top-right corner
|
|
ctx.beginPath();
|
|
ctx.arc(x + width, y, cornerSize/2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Bottom-left corner
|
|
ctx.beginPath();
|
|
ctx.arc(x, y + height, cornerSize/2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Bottom-right corner
|
|
ctx.beginPath();
|
|
ctx.arc(x + width, y + height, cornerSize/2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Inner corner dots
|
|
ctx.fillStyle = '#1a0a00';
|
|
const innerDot = 4;
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, innerDot/2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.arc(x + width, y, innerDot/2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.arc(x, y + height, innerDot/2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.beginPath();
|
|
ctx.arc(x + width, y + height, innerDot/2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
};
|
|
|
|
Scene_Map.prototype.drawMinimapLegend = function() {
|
|
const bitmap = this._minimapOverlay.bitmap;
|
|
const legendX = 18;
|
|
const legendY = 50;
|
|
const dotSize = 10;
|
|
const lineHeight = 24;
|
|
const ctx = bitmap.context;
|
|
|
|
bitmap.fontSize = 14;
|
|
bitmap.textColor = '#c9a959';
|
|
bitmap.outlineColor = '#1a0a00';
|
|
bitmap.outlineWidth = 3;
|
|
|
|
// Legend title with underline
|
|
bitmap.drawText('Legend', legendX, legendY, 100, 18, 'left');
|
|
ctx.strokeStyle = '#8b7355';
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath();
|
|
ctx.moveTo(legendX, legendY + 18);
|
|
ctx.lineTo(legendX + 55, legendY + 18);
|
|
ctx.stroke();
|
|
|
|
bitmap.textColor = '#a89070';
|
|
|
|
// Player marker
|
|
ctx.fillStyle = '#cc0000';
|
|
ctx.beginPath();
|
|
ctx.arc(legendX + dotSize/2, legendY + lineHeight + dotSize/2 + 5, dotSize/2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.strokeStyle = '#ffcc00';
|
|
ctx.lineWidth = 2;
|
|
ctx.stroke();
|
|
bitmap.drawText('You', legendX + dotSize + 10, legendY + lineHeight + 5, 100, 18, 'left');
|
|
|
|
// NPC marker (warm gold)
|
|
ctx.fillStyle = '#daa520';
|
|
ctx.beginPath();
|
|
ctx.arc(legendX + dotSize/2, legendY + lineHeight * 2 + dotSize/2 + 5, dotSize/2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.strokeStyle = '#2a1a00';
|
|
ctx.lineWidth = 1;
|
|
ctx.stroke();
|
|
bitmap.drawText('NPC', legendX + dotSize + 10, legendY + lineHeight * 2 + 5, 100, 18, 'left');
|
|
|
|
// Locked door/chest marker (teal/mysterious)
|
|
ctx.fillStyle = '#40e0d0';
|
|
ctx.beginPath();
|
|
ctx.arc(legendX + dotSize/2, legendY + lineHeight * 3 + dotSize/2 + 5, dotSize/2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.strokeStyle = '#2a1a00';
|
|
ctx.lineWidth = 1;
|
|
ctx.stroke();
|
|
bitmap.drawText('Locked', legendX + dotSize + 10, legendY + lineHeight * 3 + 5, 150, 18, 'left');
|
|
|
|
// Exit/Transfer marker (eerie green)
|
|
ctx.fillStyle = '#7cfc00';
|
|
ctx.beginPath();
|
|
ctx.arc(legendX + dotSize/2, legendY + lineHeight * 4 + dotSize/2 + 5, dotSize/2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.strokeStyle = '#2a1a00';
|
|
ctx.lineWidth = 1;
|
|
ctx.stroke();
|
|
bitmap.drawText('Passage', legendX + dotSize + 10, legendY + lineHeight * 4 + 5, 150, 18, 'left');
|
|
};
|
|
|
|
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;
|
|
|
|
// Dark fantasy color palette
|
|
const wallColor = '#1a1410'; // Very dark brown/black for walls
|
|
const floorColor = '#5a4f45'; // Lighter dusty floor (more contrast)
|
|
const floorAltColor = '#4d4238'; // Floor variation
|
|
|
|
// 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 - use checkPassage for more accurate detection
|
|
// checkPassage checks the actual tile bits rather than just directional passability
|
|
const bit = 0x0f; // All directions
|
|
const passage = $gameMap.checkPassage(x, y, bit);
|
|
|
|
// Also check standard passability as backup
|
|
const dirPassable = $gameMap.isPassable(x, y, 2) ||
|
|
$gameMap.isPassable(x, y, 4) ||
|
|
$gameMap.isPassable(x, y, 6) ||
|
|
$gameMap.isPassable(x, y, 8);
|
|
|
|
// Check if any tile at this location is a "void" tile (like pits)
|
|
// by examining the actual tile IDs
|
|
const tiles = $gameMap.allTiles(x, y);
|
|
let hasFloorTile = false;
|
|
|
|
// Look for actual floor/ground tiles
|
|
// RPG Maker MZ Tileset Layout:
|
|
// A1 (animated/water): 2048-2815 - water, waterfalls, poison swamps, lava
|
|
// A2 (ground): 2816-4351 - grass, dirt, stone floors
|
|
// A3 (buildings): 4352-5887 - roofs and building exteriors
|
|
// A4 (walls): 5888-8191 - wall tops
|
|
// A5 (normal tiles): 1536-1663 - simple ground tiles
|
|
// B-E tiles: start at different offsets for decoration
|
|
|
|
for (const tileId of tiles) {
|
|
if (tileId === 0) continue;
|
|
|
|
// A5 tiles (simple ground): 1536-1663
|
|
if (tileId >= 1536 && tileId < 1664) {
|
|
hasFloorTile = true;
|
|
break;
|
|
}
|
|
// A1 tiles (water/animated - including poison, lava, swamps): 2048-2815
|
|
if (tileId >= 2048 && tileId < 2816) {
|
|
hasFloorTile = true;
|
|
break;
|
|
}
|
|
// A2 tiles (ground autotiles): 2816-4351
|
|
if (tileId >= 2816 && tileId < 4352) {
|
|
hasFloorTile = true;
|
|
break;
|
|
}
|
|
// B-E tiles can also be walkable floors (tile ID encoding varies)
|
|
// These are typically in lower ranges and used for details/overlays
|
|
// If a B-E tile is passable, it should be considered ground
|
|
if (tileId > 0 && tileId < 1536) {
|
|
// This is a B-E tile, check if it's passable via flags
|
|
const flags = $gameMap.tilesetFlags();
|
|
const flag = flags[tileId];
|
|
if (flag !== undefined && (flag & 0x10) === 0) {
|
|
// Not star passability (not an overlay) - could be floor
|
|
if ((flag & 0x0F) !== 0x0F) {
|
|
// Not fully blocked
|
|
hasFloorTile = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Consider passable only if: passage check passes AND has a floor tile
|
|
// OR if standard directional passability says it's passable AND has floor
|
|
const isPassable = (passage || dirPassable) && hasFloorTile;
|
|
|
|
// Get terrain tag for variety
|
|
const terrainTag = $gameMap.terrainTag(x, y);
|
|
const regionId = $gameMap.regionId(x, y);
|
|
|
|
// Color based on passability and terrain - dark fantasy palette
|
|
let color;
|
|
if (!isPassable) {
|
|
// Impassable - dark stone walls
|
|
color = wallColor;
|
|
// Add slight variation
|
|
if ((x * 7 + y * 13) % 5 === 0) {
|
|
color = '#352a24';
|
|
}
|
|
} else if (terrainTag > 0) {
|
|
// Special terrain - different dungeon features
|
|
const terrainColors = [
|
|
'#3d4a3d', // Mossy
|
|
'#4a4a3d', // Sandy
|
|
'#3d3d4a', // Damp stone
|
|
'#4a3d3d', // Bloodstained
|
|
'#3d4a4a', // Flooded
|
|
'#4a3d4a', // Corrupted
|
|
'#4a4a4a' // Ashen
|
|
];
|
|
color = terrainColors[(terrainTag - 1) % terrainColors.length];
|
|
} else {
|
|
// Normal passable floor - aged stone
|
|
color = (x + y) % 2 === 0 ? floorColor : floorAltColor;
|
|
}
|
|
|
|
// Add noise for texture
|
|
const noise = ((x * 17 + y * 31) % 10) - 5;
|
|
color = this.adjustColor(color, noise);
|
|
|
|
bitmap.fillRect(px, py, tileWidth, tileHeight, color);
|
|
|
|
// Draw subtle worn grid lines (like old stone tiles)
|
|
const ctx = bitmap.context;
|
|
ctx.strokeStyle = 'rgba(0, 0, 0, 0.15)';
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeRect(px, py, tileWidth, tileHeight);
|
|
|
|
// Add occasional cracks/details on floors
|
|
if (isPassable && (x * 23 + y * 37) % 20 === 0) {
|
|
ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)';
|
|
ctx.beginPath();
|
|
ctx.moveTo(px + 2, py + 2);
|
|
ctx.lineTo(px + tileWidth - 4, py + tileHeight - 4);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw important events as markers
|
|
for (const event of $gameMap.events()) {
|
|
if (!event) continue;
|
|
|
|
const ex = event.x * tileWidth + tileWidth / 2;
|
|
const ey = event.y * tileHeight + tileHeight / 2;
|
|
const ctx = bitmap.context;
|
|
const markerSize = Math.min(tileWidth, tileHeight) / 2.5;
|
|
|
|
// Check event types in priority order
|
|
if (this.isLockedDoorOrChest(event)) {
|
|
// Draw locked door/chest marker (teal/mysterious)
|
|
ctx.fillStyle = '#40e0d0';
|
|
ctx.shadowColor = '#40e0d0';
|
|
ctx.shadowBlur = 4;
|
|
ctx.beginPath();
|
|
ctx.arc(ex, ey, markerSize, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.shadowBlur = 0;
|
|
ctx.strokeStyle = '#1a3a3a';
|
|
ctx.lineWidth = 2;
|
|
ctx.stroke();
|
|
} else if (this.isMapTransfer(event)) {
|
|
// Draw exit/entrance marker (eerie green glow)
|
|
ctx.fillStyle = '#7cfc00';
|
|
ctx.shadowColor = '#7cfc00';
|
|
ctx.shadowBlur = 6;
|
|
ctx.beginPath();
|
|
ctx.arc(ex, ey, markerSize, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.shadowBlur = 0;
|
|
ctx.strokeStyle = '#2a4a00';
|
|
ctx.lineWidth = 2;
|
|
ctx.stroke();
|
|
} else if (this.isImportantEvent(event)) {
|
|
// Draw NPC marker (warm gold)
|
|
ctx.fillStyle = '#daa520';
|
|
ctx.shadowColor = '#daa520';
|
|
ctx.shadowBlur = 3;
|
|
ctx.beginPath();
|
|
ctx.arc(ex, ey, markerSize, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.shadowBlur = 0;
|
|
ctx.strokeStyle = '#4a3500';
|
|
ctx.lineWidth = 2;
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
};
|
|
|
|
// Check if an event is a locked door, chest, or barred gate
|
|
Scene_Map.prototype.isLockedDoorOrChest = function(event) {
|
|
if (!event) return false;
|
|
|
|
// Get the event data
|
|
const eventData = event.event();
|
|
if (!eventData) return false;
|
|
|
|
const name = eventData.name;
|
|
|
|
// Check for locked door patterns
|
|
if (name.includes('鍵扉')) return true; // "Locked door"
|
|
|
|
// Check for chest patterns
|
|
if (name.includes('宝箱')) return true; // "Treasure chest"
|
|
|
|
// Check for barred door/gate patterns (iron bars)
|
|
if (name.includes('鉄格子')) return true; // "Iron bars/Barred gate"
|
|
|
|
// Check for barred/latch doors (opens from above/below pattern)
|
|
if (name.includes('上から') && name.includes('下から')) return true; // "From above... from below" latch doors
|
|
|
|
return false;
|
|
};
|
|
|
|
// Check if an event transfers to another map
|
|
Scene_Map.prototype.isMapTransfer = function(event) {
|
|
if (!event) return false;
|
|
|
|
// Get the event data
|
|
const eventData = event.event();
|
|
if (!eventData) return false;
|
|
|
|
const name = eventData.name;
|
|
|
|
// Check for transfer event name patterns
|
|
if (name.includes('移動')) return true; // "Transfer/Move"
|
|
|
|
// Check the current page for Transfer Player command (code 201)
|
|
const page = event.page();
|
|
if (!page) return false;
|
|
|
|
const list = page.list;
|
|
if (!list) return false;
|
|
|
|
for (const cmd of list) {
|
|
if (cmd.code === 201) { // Transfer Player command
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
// Check if an event is a locked door that can be picked (legacy - kept for compatibility)
|
|
Scene_Map.prototype.isLockedDoor = function(event) {
|
|
return this.isLockedDoorOrChest(event);
|
|
};
|
|
|
|
// 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.isTilePassable = function(x, y) {
|
|
// First check: is this tile visually a "ceiling/void" tile?
|
|
// These are often star-passable but should show as walls on minimap
|
|
const tiles = $gameMap.allTiles(x, y);
|
|
const flags = $gameMap.tilesetFlags();
|
|
|
|
for (const tileId of tiles) {
|
|
if (tileId === 0) continue;
|
|
|
|
// A4 wall-top tiles (2048-2815) - these are wall tops/ceilings
|
|
// A5 floor tiles start at 1536, A4 starts at 2048
|
|
if (tileId >= 2048 && tileId < 2816) {
|
|
// This is an A4 wall-top tile - treat as impassable for minimap
|
|
return false;
|
|
}
|
|
|
|
// Check for tiles in the B-E range that have star passability
|
|
// Star passable (0x10) tiles that are in upper layers are usually ceilings/walls
|
|
if (tileId >= 256) { // B-E tiles
|
|
const flag = flags[tileId];
|
|
if (flag !== undefined && (flag & 0x10) !== 0) {
|
|
// Star passable - this is likely a ceiling tile, treat as wall
|
|
// Unless it's actually ground level
|
|
const layerTiles = $gameMap.layeredTiles(x, y);
|
|
if (layerTiles.length > 0 && layerTiles[0] === tileId) {
|
|
// This star tile is on the top layer - it's a ceiling/wall
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check standard directional passability (all 4 directions)
|
|
const passableDown = $gameMap.isPassable(x, y, 2);
|
|
const passableLeft = $gameMap.isPassable(x, y, 4);
|
|
const passableRight = $gameMap.isPassable(x, y, 6);
|
|
const passableUp = $gameMap.isPassable(x, y, 8);
|
|
|
|
// If any direction is passable, consider it a floor
|
|
if (passableDown || passableLeft || passableRight || passableUp) {
|
|
// But also check if there's a blocking event at this location
|
|
const events = $gameMap.eventsXy(x, y);
|
|
for (const event of events) {
|
|
if (event && !event.isThrough() && event.isNormalPriority()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
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 - mystical/atmospheric style
|
|
this._minimapPlayerMarker = new Sprite();
|
|
this._minimapPlayerMarker.bitmap = new Bitmap(36, 36);
|
|
|
|
const ctx = this._minimapPlayerMarker.bitmap.context;
|
|
const cx = 18, cy = 18;
|
|
|
|
// Ethereal outer glow - deep crimson
|
|
const outerGlow = ctx.createRadialGradient(cx, cy, 0, cx, cy, 16);
|
|
outerGlow.addColorStop(0, 'rgba(139, 69, 19, 0)');
|
|
outerGlow.addColorStop(0.5, 'rgba(139, 69, 19, 0.3)');
|
|
outerGlow.addColorStop(1, 'rgba(139, 69, 19, 0)');
|
|
ctx.fillStyle = outerGlow;
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy, 16, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Main marker - mystical blood red
|
|
const mainGrad = ctx.createRadialGradient(cx, cy, 0, cx, cy, 10);
|
|
mainGrad.addColorStop(0, '#c41e3a');
|
|
mainGrad.addColorStop(0.7, '#8b0000');
|
|
mainGrad.addColorStop(1, '#4a0000');
|
|
ctx.fillStyle = mainGrad;
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy, 10, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Inner glow - warm amber
|
|
const innerGrad = ctx.createRadialGradient(cx, cy, 0, cx, cy, 6);
|
|
innerGrad.addColorStop(0, '#ffd700');
|
|
innerGrad.addColorStop(0.5, '#daa520');
|
|
innerGrad.addColorStop(1, '#b8860b');
|
|
ctx.fillStyle = innerGrad;
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy, 6, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Center soul light
|
|
ctx.fillStyle = '#fff8dc';
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy, 2, 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;
|
|
};
|
|
|
|
})();
|