//============================================================================= // MinimapZoom.js //============================================================================= /*: * @plugindesc Adds a minimap/zoom-out feature with exit labels, zoom and pan controls. * @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. * * Controls: * - M: Close minimap * - Mouse Wheel / Shift / Ctrl: Zoom in/out * - Arrow Keys / Mouse Drag: Pan the map * - R: Reset zoom and pan * * Features: * - Exit locations are numbered and show destination names in a legend * - Nearby exits to the same location are grouped together * - Zoom and pan for easier viewing of large maps * - Fog of War: Only explored areas are revealed on the minimap * - Exit Discovery: Exits are only shown after the player uses them * * Settings (Options Menu): * - "Minimap Mod (M)": Enable/disable the minimap feature entirely * - "Fog of War": Dark overlay hiding unexplored map areas * - "Hide Exits": Hide exits/locations until discovered by the player * * Combinations: * - Both ON: Full exploration mode (dark fog + hidden exits) * - Fog OFF, Exits ON: See full map but exits hidden until used * - Fog ON, Exits OFF: Dark fog but all exits visible * - Both OFF: See everything immediately */ (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; // Zoom and pan state let minimapZoom = 1.0; let minimapPanX = 0; let minimapPanY = 0; const MIN_ZOOM = 0.5; const MAX_ZOOM = 3.0; const ZOOM_STEP = 0.1; const PAN_SPEED = 15; // Mouse drag state let isDragging = false; let dragStartX = 0; let dragStartY = 0; let dragStartPanX = 0; let dragStartPanY = 0; // Exit location data let exitLocations = []; // Fog of war - exploration tracking const EXPLORE_RADIUS = 5; // Tiles revealed around player // Export minimap state for other plugins/systems to check window.isMinimapActive = function() { return minimapActive; }; // Function to close minimap from anywhere (used by event start) window.closeMinimapIfActive = function() { if (minimapActive && SceneManager._scene && SceneManager._scene.hideMinimap) { minimapActive = false; SceneManager._scene.hideMinimap(); SoundManager.playCancel(); } }; //========================================================================= // ConfigManager - Add minimap enabled setting //========================================================================= // Default values (enabled by default) ConfigManager.minimapEnabled = true; ConfigManager.minimapFogOfWar = true; // Fog of war (dark overlay) enabled by default ConfigManager.minimapRevealAll = false; // Reveal all destinations OFF by default (exploration mode) // Hook into makeData to save the settings const _ConfigManager_makeData = ConfigManager.makeData; ConfigManager.makeData = function() { const config = _ConfigManager_makeData.call(this); config.minimapEnabled = this.minimapEnabled; config.minimapFogOfWar = this.minimapFogOfWar; config.minimapRevealAll = this.minimapRevealAll; return config; }; // Hook into applyData to load the settings const _ConfigManager_applyData = ConfigManager.applyData; ConfigManager.applyData = function(config) { _ConfigManager_applyData.call(this, config); this.minimapEnabled = this.readFlag(config, "minimapEnabled", true); this.minimapFogOfWar = this.readFlag(config, "minimapFogOfWar", true); this.minimapRevealAll = this.readFlag(config, "minimapRevealAll", false); }; //========================================================================= // Window_Options - Add minimap toggle option //========================================================================= const _Window_Options_makeCommandList = Window_Options.prototype.makeCommandList; Window_Options.prototype.makeCommandList = function() { _Window_Options_makeCommandList.call(this); this.addCommand("Minimap Mod (M)", "minimapEnabled"); this.addCommand(" Fog of War", "minimapFogOfWar"); this.addCommand(" Reveal Destinations", "minimapRevealAll"); }; //========================================================================= // Input - Add minimap key and zoom/pan keys //========================================================================= Input.keyMapper[toggleKeyCode] = 'minimap'; Input.keyMapper[82] = 'minimapReset'; // R key // Note: Shift (16) and Ctrl (17) are checked directly in updateMinimapZoomPan // to avoid interfering with game controls when minimap is not active //========================================================================= // Auto-close minimap when any event starts //========================================================================= // When any event starts, auto-close the minimap const _Game_Event_start = Game_Event.prototype.start; Game_Event.prototype.start = function() { // Close minimap if it's open when an event tries to start window.closeMinimapIfActive(); _Game_Event_start.call(this); }; //========================================================================= // Exploration Tracking - Fog of War System //========================================================================= // Initialize exploration data on Game_System const _Game_System_initialize = Game_System.prototype.initialize; Game_System.prototype.initialize = function() { _Game_System_initialize.call(this); this._exploredMaps = {}; this._activatedExits = {}; // Track which exits have been used }; // Get explored tiles for current map Game_System.prototype.getExploredTiles = function(mapId) { if (!this._exploredMaps) { this._exploredMaps = {}; } if (!this._exploredMaps[mapId]) { this._exploredMaps[mapId] = {}; } return this._exploredMaps[mapId]; }; // Mark a tile as explored Game_System.prototype.exploreTile = function(mapId, x, y) { const explored = this.getExploredTiles(mapId); const key = x + ',' + y; explored[key] = true; }; // Check if a tile is explored Game_System.prototype.isTileExplored = function(mapId, x, y) { const explored = this.getExploredTiles(mapId); const key = x + ',' + y; return explored[key] === true; }; // Mark an exit as activated (used by player) Game_System.prototype.activateExit = function(fromMapId, toMapId, x, y) { if (!this._activatedExits) { this._activatedExits = {}; } if (!this._activatedExits[fromMapId]) { this._activatedExits[fromMapId] = {}; } // Key by destination map and approximate position (to handle grouped exits) const key = toMapId + '_' + Math.floor(x / 4) + '_' + Math.floor(y / 4); this._activatedExits[fromMapId][key] = true; // Also store exact position for precise matching const exactKey = toMapId + '_' + x + '_' + y; this._activatedExits[fromMapId][exactKey] = true; }; // Check if an exit has been activated Game_System.prototype.isExitActivated = function(fromMapId, toMapId, x, y) { if (!this._activatedExits) { this._activatedExits = {}; } if (!this._activatedExits[fromMapId]) { return false; } // Check exact position first const exactKey = toMapId + '_' + x + '_' + y; if (this._activatedExits[fromMapId][exactKey]) { return true; } // Check grouped position const key = toMapId + '_' + Math.floor(x / 4) + '_' + Math.floor(y / 4); return this._activatedExits[fromMapId][key] === true; }; // Reveal tiles around a position Game_System.prototype.revealAroundPosition = function(mapId, centerX, centerY, radius) { for (let dx = -radius; dx <= radius; dx++) { for (let dy = -radius; dy <= radius; dy++) { // Use circular radius if (dx * dx + dy * dy <= radius * radius) { const x = centerX + dx; const y = centerY + dy; if (x >= 0 && y >= 0 && x < $gameMap.width() && y < $gameMap.height()) { this.exploreTile(mapId, x, y); } } } } }; // Track when player uses a transfer command const _Game_Interpreter_command201 = Game_Interpreter.prototype.command201; Game_Interpreter.prototype.command201 = function(params) { // params[0]: 0 = direct, 1 = variable designation // params[1]: map ID (or variable ID) // params[2]: x (or variable ID) // params[3]: y (or variable ID) // Get actual values (handle both direct and variable designation) let toMapId, toX, toY; if (params[0] === 0) { toMapId = params[1]; toX = params[2]; toY = params[3]; } else { toMapId = $gameVariables.value(params[1]); toX = $gameVariables.value(params[2]); toY = $gameVariables.value(params[3]); } const fromMapId = $gameMap.mapId(); // Use the event's position as the exit location const event = $gameMap.event(this._eventId); if (event) { $gameSystem.activateExit(fromMapId, toMapId, event.x, event.y); } // Also record the arrival exit (where player comes out on destination map) $gameSystem.activateExit(toMapId, fromMapId, toX, toY); // Store info about where we came from so we can reveal the return exit $gameSystem._pendingEntryReveal = { destinationMapId: toMapId, sourceMapId: fromMapId, landingX: toX, landingY: toY }; return _Game_Interpreter_command201.call(this, params); }; // Find nearby exit events and activate them (so they show on minimap) Game_System.prototype.activateNearbyExits = function(mapId, targetMapId, nearX, nearY) { for (const event of $gameMap.events()) { if (!event) continue; // Only check events within reasonable distance const dx = Math.abs(event.x - nearX); const dy = Math.abs(event.y - nearY); if (dx > 15 || dy > 15) continue; const eventData = event.event(); if (!eventData) continue; // Check all pages for Transfer Player command leading to target map for (const page of eventData.pages) { if (!page || !page.list) continue; for (const cmd of page.list) { if (cmd.code === 201) { // Transfer Player command const params = cmd.parameters; let cmdTargetMapId = params[0] === 0 ? params[1] : $gameVariables.value(params[1]); if (cmdTargetMapId === targetMapId) { // Reveal and activate this exit this.revealAroundPosition(mapId, event.x, event.y, EXPLORE_RADIUS); this.activateExit(mapId, targetMapId, event.x, event.y); } break; } } } } }; // Track player movement to reveal tiles const _Game_Player_moveStraight = Game_Player.prototype.moveStraight; Game_Player.prototype.moveStraight = function(d) { _Game_Player_moveStraight.call(this, d); if (this.isMovementSucceeded()) { $gameSystem.revealAroundPosition($gameMap.mapId(), this.x, this.y, EXPLORE_RADIUS); } }; // Also reveal on map setup (when entering a new map or loading) const _Game_Player_performTransfer = Game_Player.prototype.performTransfer; Game_Player.prototype.performTransfer = function() { _Game_Player_performTransfer.call(this); if ($gameMap.mapId() > 0) { // Reveal around player's new position $gameSystem.revealAroundPosition($gameMap.mapId(), this.x, this.y, EXPLORE_RADIUS); // Note: Exit reveal is handled in Scene_Map.start after map is fully loaded } }; // Reveal on game load as well - and handle pending entry reveals const _Scene_Map_start = Scene_Map.prototype.start; Scene_Map.prototype.start = function() { _Scene_Map_start.call(this); if ($gameMap.mapId() > 0) { $gameSystem.revealAroundPosition($gameMap.mapId(), $gamePlayer.x, $gamePlayer.y, EXPLORE_RADIUS); // Check for pending entry reveal - find and activate nearby exits leading back if ($gameSystem._pendingEntryReveal && $gameSystem._pendingEntryReveal.destinationMapId === $gameMap.mapId()) { const entry = $gameSystem._pendingEntryReveal; $gameSystem.activateNearbyExits( $gameMap.mapId(), entry.sourceMapId, entry.landingX, entry.landingY ); $gameSystem._pendingEntryReveal = null; } } }; //========================================================================= // 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() { // Check if minimap is enabled in options if (!ConfigManager.minimapEnabled) { // If minimap is currently active but disabled, close it if (minimapActive) { minimapActive = false; this.hideMinimap(); } return; // Don't process minimap inputs when disabled } // Allow closing minimap anytime (even during events/messages), but only open when not busy if (Input.isTriggered('minimap')) { if (minimapActive) { // Always allow closing this.toggleMinimap(); } else if (!$gameMessage.isBusy() && !$gameMap.isEventRunning()) { // Only open when not busy this.toggleMinimap(); } } // Auto-close minimap if an event starts running or message appears if (minimapActive && ($gameMap.isEventRunning() || $gameMessage.isBusy())) { minimapActive = false; this.hideMinimap(); SoundManager.playCancel(); } // Update player marker position and atmospheric effects if minimap is active if (minimapActive) { // Handle zoom and pan controls this.updateMinimapZoomPan(); if (this._minimapPlayerMarker) { this.updateMinimapPlayerMarker(); } this.updateMinimapAtmosphere(); } }; Scene_Map.prototype.collectExitLocations = function() { exitLocations = []; const rawExits = []; const mapId = $gameMap.mapId(); // Collect all transfer events (show all if revealAll is ON, otherwise filter by exploration) const revealAll = ConfigManager.minimapRevealAll; for (const event of $gameMap.events()) { if (!event) continue; // Only include exits in explored areas (unless revealAll is enabled) if (!revealAll && !$gameSystem.isTileExplored(mapId, event.x, event.y)) { continue; } const eventData = event.event(); if (!eventData) continue; // Check all pages for Transfer Player command for (const page of eventData.pages) { if (!page || !page.list) continue; for (const cmd of page.list) { if (cmd.code === 201) { // Transfer Player command const params = cmd.parameters; if (params[0] === 0) { // Direct designation (not variable) const targetMapId = params[1]; // Only show exits that have been activated (used by player) - unless revealAll is enabled if (!revealAll && !$gameSystem.isExitActivated(mapId, targetMapId, event.x, event.y)) { continue; } const mapName = this.getMapName(targetMapId); rawExits.push({ x: event.x, y: event.y, mapId: targetMapId, mapName: mapName }); } break; // Only need first transfer per page } } } } // Group nearby exits going to the same destination const GROUPING_DISTANCE = 3; // Tiles within this distance get grouped const grouped = []; const used = new Set(); for (let i = 0; i < rawExits.length; i++) { if (used.has(i)) continue; const exit = rawExits[i]; const group = { positions: [{x: exit.x, y: exit.y}], mapId: exit.mapId, mapName: exit.mapName }; used.add(i); // Find nearby exits to same destination for (let j = i + 1; j < rawExits.length; j++) { if (used.has(j)) continue; const other = rawExits[j]; if (other.mapId === exit.mapId) { // Check if any position in group is close enough let isNearby = false; for (const pos of group.positions) { const dx = Math.abs(other.x - pos.x); const dy = Math.abs(other.y - pos.y); if (dx <= GROUPING_DISTANCE && dy <= GROUPING_DISTANCE) { isNearby = true; break; } } if (isNearby) { group.positions.push({x: other.x, y: other.y}); used.add(j); } } } // Calculate center position for the group let sumX = 0, sumY = 0; for (const pos of group.positions) { sumX += pos.x; sumY += pos.y; } group.centerX = sumX / group.positions.length; group.centerY = sumY / group.positions.length; grouped.push(group); } // Assign numbers to grouped exits let exitNumber = 1; for (const group of grouped) { group.number = exitNumber++; exitLocations.push(group); } }; Scene_Map.prototype.getMapName = function(mapId) { // Try to get map name from $dataMapInfos if ($dataMapInfos && $dataMapInfos[mapId]) { return $dataMapInfos[mapId].name || 'Unknown'; } return 'Map ' + mapId; }; Scene_Map.prototype.updateMinimapZoomPan = function() { let zoomChanged = false; let panChanged = false; // Zoom controls - check raw key state directly only when minimap is active // This prevents Shift/Ctrl from being captured when minimap is closed const shiftPressed = Input._currentState['shift']; const ctrlPressed = Input._currentState['control']; if (shiftPressed) { minimapZoom = Math.min(MAX_ZOOM, minimapZoom + ZOOM_STEP); zoomChanged = true; } if (ctrlPressed) { minimapZoom = Math.max(MIN_ZOOM, minimapZoom - ZOOM_STEP); zoomChanged = true; } // Pan controls (arrow keys) if (Input.isPressed('up')) { minimapPanY += PAN_SPEED; panChanged = true; } if (Input.isPressed('down')) { minimapPanY -= PAN_SPEED; panChanged = true; } if (Input.isPressed('left')) { minimapPanX += PAN_SPEED; panChanged = true; } if (Input.isPressed('right')) { minimapPanX -= PAN_SPEED; panChanged = true; } // Reset controls if (Input.isTriggered('minimapReset')) { minimapZoom = 1.0; minimapPanX = 0; minimapPanY = 0; zoomChanged = true; panChanged = true; SoundManager.playCursor(); } // Mouse wheel zoom if (this._minimapContent) { const wheelY = TouchInput.wheelY; if (wheelY !== 0) { if (wheelY < 0) { minimapZoom = Math.min(MAX_ZOOM, minimapZoom + ZOOM_STEP); } else { minimapZoom = Math.max(MIN_ZOOM, minimapZoom - ZOOM_STEP); } zoomChanged = true; } } // Mouse drag pan if (TouchInput.isPressed()) { if (!isDragging) { isDragging = true; dragStartX = TouchInput.x; dragStartY = TouchInput.y; dragStartPanX = minimapPanX; dragStartPanY = minimapPanY; } else { minimapPanX = dragStartPanX + (TouchInput.x - dragStartX); minimapPanY = dragStartPanY + (TouchInput.y - dragStartY); panChanged = true; } } else { isDragging = false; } // Apply zoom and pan to content if ((zoomChanged || panChanged) && this._minimapContent) { this.applyMinimapTransform(); } }; Scene_Map.prototype.applyMinimapTransform = function() { if (!this._minimapContent) return; // Calculate the center point for zooming const centerX = Graphics.width / 2; const centerY = Graphics.height / 2; // Apply scale this._minimapContent.scale.x = this._minimapScale * minimapZoom; this._minimapContent.scale.y = this._minimapScale * minimapZoom; // Calculate new offset to keep centered const mapWidth = $gameMap.width() * this._minimapTileWidth; const mapHeight = $gameMap.height() * this._minimapTileHeight; const scaledWidth = mapWidth * this._minimapScale * minimapZoom; const scaledHeight = mapHeight * this._minimapScale * minimapZoom; const baseOffsetX = (Graphics.width - scaledWidth) / 2; const baseOffsetY = (Graphics.height - scaledHeight) / 2; this._minimapContent.x = baseOffsetX + minimapPanX; this._minimapContent.y = baseOffsetY + minimapPanY; // Update border to match content if (this._minimapBorder) { this._minimapBorder.scale.x = this._minimapScale * minimapZoom; this._minimapBorder.scale.y = this._minimapScale * minimapZoom; this._minimapBorder.x = baseOffsetX + minimapPanX - this._borderPadding * this._minimapScale * minimapZoom; this._minimapBorder.y = baseOffsetY + minimapPanY - this._borderPadding * this._minimapScale * minimapZoom; } // Update exit destinations panel to stay next to map (right side) if (this._exitDestinationsPanel) { this._exitDestinationsPanel.scale.x = minimapZoom; this._exitDestinationsPanel.scale.y = minimapZoom; this._exitDestinationsPanel.x = baseOffsetX + minimapPanX + scaledWidth + this._exitPanelOffset * minimapZoom; this._exitDestinationsPanel.y = baseOffsetY + minimapPanY; } // Update legend panel to stay next to map (left side) if (this._legendPanel) { this._legendPanel.scale.x = minimapZoom; this._legendPanel.scale.y = minimapZoom; const legendWidth = 120 * minimapZoom; this._legendPanel.x = baseOffsetX + minimapPanX - legendWidth - this._legendPanelOffset * minimapZoom; this._legendPanel.y = baseOffsetY + minimapPanY; } // Update title panel to stay above map (centered) if (this._titlePanel) { this._titlePanel.scale.x = minimapZoom; this._titlePanel.scale.y = minimapZoom; this._titlePanel.x = baseOffsetX + minimapPanX + scaledWidth / 2; this._titlePanel.y = baseOffsetY + minimapPanY - this._titlePanelOffset * minimapZoom; } // Update controls panel to stay below map (centered) if (this._controlsPanel) { this._controlsPanel.scale.x = minimapZoom; this._controlsPanel.scale.y = minimapZoom; this._controlsPanel.x = baseOffsetX + minimapPanX + scaledWidth / 2; this._controlsPanel.y = baseOffsetY + minimapPanY + scaledHeight + this._controlsPanelOffset * minimapZoom; } // Store current offsets for marker positioning this._currentOffsetX = this._minimapContent.x; this._currentOffsetY = this._minimapContent.y; this._currentScale = this._minimapScale * minimapZoom; // Update exit markers positions if (this._exitMarkers) { this.updateExitMarkerPositions(); } }; 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; // Reset zoom and pan when opening minimapZoom = 1.0; minimapPanX = 0; minimapPanY = 0; isDragging = false; // Collect exit location data this.collectExitLocations(); // 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(); // Create border as separate sprite that moves with content this.createMinimapBorder(mapPixelWidth, mapPixelHeight); this.addChild(this._minimapOverlay); this.addChild(this._minimapBorder); this.addChild(this._minimapContent); // Create title panel (moves with map) - at top this.createTitlePanel(mapPixelWidth, mapPixelHeight); // Create controls panel (moves with map) - at bottom this.createControlsPanel(mapPixelWidth, mapPixelHeight); // Create legend panel (moves with map) - on left side this.createLegendPanel(mapPixelWidth, mapPixelHeight); // Create exit destinations panel (moves with map) - on right side this.createExitDestinationsPanel(mapPixelWidth, mapPixelHeight); // Create exit markers (as separate sprites for positioning with zoom/pan) this.createExitMarkers(); // Create player marker this.createMinimapPlayerMarker(); // Initialize current transform values and position all elements correctly this._currentOffsetX = offsetX; this._currentOffsetY = offsetY; this._currentScale = scale; this.applyMinimapTransform(); }; 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.createMinimapBorder = function(mapPixelWidth, mapPixelHeight) { const borderSize = 6; const padding = 20; // Extra padding for the border sprite // Create border sprite sized to fit around the map this._minimapBorder = new Sprite(); const borderWidth = mapPixelWidth + padding * 2; const borderHeight = mapPixelHeight + padding * 2; this._minimapBorder.bitmap = new Bitmap(borderWidth, borderHeight); const ctx = this._minimapBorder.bitmap.context; const x = padding; const y = padding; const width = mapPixelWidth; const height = mapPixelHeight; // 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(); // Store the padding offset for positioning this._borderPadding = padding; // Position and scale to match content this._minimapBorder.x = this._minimapOffsetX - padding * this._minimapScale; this._minimapBorder.y = this._minimapOffsetY - padding * this._minimapScale; this._minimapBorder.scale.x = this._minimapScale; this._minimapBorder.scale.y = this._minimapScale; }; Scene_Map.prototype.createTitlePanel = function(mapPixelWidth, mapPixelHeight) { const panelWidth = 350; const panelHeight = 65; // Create title sprite this._titlePanel = new Sprite(); this._titlePanel.bitmap = new Bitmap(panelWidth, panelHeight); const bitmap = this._titlePanel.bitmap; const ctx = bitmap.context; // Draw background panel ctx.fillStyle = 'rgba(26, 20, 16, 0.9)'; ctx.fillRect(0, 0, panelWidth, panelHeight); ctx.strokeStyle = '#8b7355'; ctx.lineWidth = 2; ctx.strokeRect(0, 0, panelWidth, panelHeight); // Inner border ctx.strokeStyle = '#c9a959'; ctx.lineWidth = 1; ctx.strokeRect(3, 3, panelWidth - 6, panelHeight - 6); bitmap.fontFace = 'GameFont'; bitmap.fontSize = 26; bitmap.textColor = '#d4af37'; bitmap.outlineColor = '#1a0a00'; bitmap.outlineWidth = 4; // Main title bitmap.drawText('~ Cartograph ~', 0, 8, panelWidth, 30, 'center'); // Map name const mapName = $gameMap.displayName() || 'Unknown Region'; bitmap.fontSize = 18; bitmap.textColor = '#c9a959'; bitmap.drawText('― ' + mapName + ' ―', 0, 38, panelWidth, 22, 'center'); // Position panel above the map (centered) this._titlePanelOffset = 15; // Gap between map and panel this._titlePanel.anchor = new Point(0.5, 1); // Anchor at bottom center this._titlePanel.x = this._minimapOffsetX + (mapPixelWidth * this._minimapScale) / 2; this._titlePanel.y = this._minimapOffsetY - this._titlePanelOffset; this.addChild(this._titlePanel); }; Scene_Map.prototype.createControlsPanel = function(mapPixelWidth, mapPixelHeight) { const panelWidth = 500; const panelHeight = 35; // Create controls sprite this._controlsPanel = new Sprite(); this._controlsPanel.bitmap = new Bitmap(panelWidth, panelHeight); const bitmap = this._controlsPanel.bitmap; const ctx = bitmap.context; // Draw background panel ctx.fillStyle = 'rgba(26, 20, 16, 0.9)'; ctx.fillRect(0, 0, panelWidth, panelHeight); ctx.strokeStyle = '#8b7355'; ctx.lineWidth = 2; ctx.strokeRect(0, 0, panelWidth, panelHeight); // Inner border ctx.strokeStyle = '#c9a959'; ctx.lineWidth = 1; ctx.strokeRect(3, 3, panelWidth - 6, panelHeight - 6); bitmap.fontFace = 'GameFont'; bitmap.fontSize = 14; bitmap.textColor = '#8b7355'; bitmap.outlineColor = '#1a0a00'; bitmap.outlineWidth = 3; // Controls text bitmap.drawText('M: Close | Arrows: Pan | Shift/Ctrl/Scroll: Zoom | R: Reset', 0, 8, panelWidth, 20, 'center'); // Position panel below the map (centered) this._controlsPanelOffset = 15; // Gap between map and panel this._controlsPanel.anchor = new Point(0.5, 0); // Anchor at top center this._controlsPanel.x = this._minimapOffsetX + (mapPixelWidth * this._minimapScale) / 2; this._controlsPanel.y = this._minimapOffsetY + (mapPixelHeight * this._minimapScale) + this._controlsPanelOffset; this.addChild(this._controlsPanel); }; // Keep old function for compatibility but it does nothing now Scene_Map.prototype.drawOrnateBorder = function(x, y, width, height) { // Border is now drawn as a separate sprite in createMinimapBorder }; Scene_Map.prototype.createLegendPanel = function(mapPixelWidth, mapPixelHeight) { const dotSize = 10; const lineHeight = 24; const padding = 15; const panelWidth = 120; const panelHeight = 35 + 5 * lineHeight + padding; // Create legend sprite this._legendPanel = new Sprite(); this._legendPanel.bitmap = new Bitmap(panelWidth, panelHeight); const bitmap = this._legendPanel.bitmap; const ctx = bitmap.context; // Draw background panel ctx.fillStyle = 'rgba(26, 20, 16, 0.9)'; ctx.fillRect(0, 0, panelWidth, panelHeight); ctx.strokeStyle = '#8b7355'; ctx.lineWidth = 2; ctx.strokeRect(0, 0, panelWidth, panelHeight); // Inner border ctx.strokeStyle = '#c9a959'; ctx.lineWidth = 1; ctx.strokeRect(3, 3, panelWidth - 6, panelHeight - 6); bitmap.fontFace = 'GameFont'; bitmap.fontSize = 14; bitmap.textColor = '#d4af37'; bitmap.outlineColor = '#1a0a00'; bitmap.outlineWidth = 3; // Title const titleY = 8; bitmap.drawText('~ Legend ~', 0, titleY, panelWidth, 18, 'center'); // Underline ctx.strokeStyle = '#8b7355'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(padding, titleY + 22); ctx.lineTo(panelWidth - padding, titleY + 22); ctx.stroke(); bitmap.fontSize = 12; bitmap.textColor = '#a89070'; const startY = 32; const legendX = padding; // Player marker ctx.fillStyle = '#cc0000'; ctx.beginPath(); ctx.arc(legendX + dotSize/2, startY + dotSize/2 + 5, dotSize/2, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#ffcc00'; ctx.lineWidth = 2; ctx.stroke(); bitmap.drawText('You', legendX + dotSize + 8, startY + 2, 80, 18, 'left'); // NPC marker (warm gold) ctx.fillStyle = '#daa520'; ctx.beginPath(); ctx.arc(legendX + dotSize/2, startY + lineHeight + dotSize/2 + 5, dotSize/2, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#2a1a00'; ctx.lineWidth = 1; ctx.stroke(); bitmap.drawText('NPC', legendX + dotSize + 8, startY + lineHeight + 2, 80, 18, 'left'); // Locked door/chest marker (teal/mysterious) ctx.fillStyle = '#40e0d0'; ctx.beginPath(); ctx.arc(legendX + dotSize/2, startY + lineHeight * 2 + dotSize/2 + 5, dotSize/2, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#2a1a00'; ctx.lineWidth = 1; ctx.stroke(); bitmap.drawText('Locked', legendX + dotSize + 8, startY + lineHeight * 2 + 2, 80, 18, 'left'); // Enemy marker (blood red) ctx.fillStyle = '#dc143c'; ctx.beginPath(); ctx.arc(legendX + dotSize/2, startY + lineHeight * 3 + dotSize/2 + 5, dotSize/2, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#2a1a00'; ctx.lineWidth = 1; ctx.stroke(); bitmap.drawText('Enemy', legendX + dotSize + 8, startY + lineHeight * 3 + 2, 80, 18, 'left'); // Exit/Transfer marker (eerie green) ctx.fillStyle = '#7cfc00'; ctx.beginPath(); ctx.arc(legendX + dotSize/2, startY + 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 + 8, startY + lineHeight * 4 + 2, 80, 18, 'left'); // Position panel to the left of the map this._legendPanelOffset = 20; // Gap between map and panel this._legendPanel.x = this._minimapOffsetX - panelWidth - this._legendPanelOffset; this._legendPanel.y = this._minimapOffsetY; this.addChild(this._legendPanel); }; // Keep old function for compatibility Scene_Map.prototype.drawMinimapLegend = function() { // Now handled by createLegendPanel }; Scene_Map.prototype.createExitDestinationsPanel = function(mapPixelWidth, mapPixelHeight) { if (exitLocations.length === 0) return; const lineHeight = 20; const padding = 15; const panelWidth = 280; const panelHeight = 35 + exitLocations.length * lineHeight + padding; // Create panel sprite this._exitDestinationsPanel = new Sprite(); this._exitDestinationsPanel.bitmap = new Bitmap(panelWidth, panelHeight); const bitmap = this._exitDestinationsPanel.bitmap; const ctx = bitmap.context; // Draw background panel ctx.fillStyle = 'rgba(26, 20, 16, 0.9)'; ctx.fillRect(0, 0, panelWidth, panelHeight); ctx.strokeStyle = '#8b7355'; ctx.lineWidth = 2; ctx.strokeRect(0, 0, panelWidth, panelHeight); // Inner border ctx.strokeStyle = '#c9a959'; ctx.lineWidth = 1; ctx.strokeRect(3, 3, panelWidth - 6, panelHeight - 6); bitmap.fontFace = 'GameFont'; bitmap.fontSize = 14; bitmap.textColor = '#d4af37'; bitmap.outlineColor = '#1a0a00'; bitmap.outlineWidth = 3; // Title const titleY = 8; bitmap.drawText('~ Destinations ~', 0, titleY, panelWidth, 18, 'center'); // Underline ctx.strokeStyle = '#8b7355'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(padding, titleY + 22); ctx.lineTo(panelWidth - padding, titleY + 22); ctx.stroke(); bitmap.fontSize = 12; bitmap.textColor = '#c9a959'; // Draw each exit destination (no limit now) for (let i = 0; i < exitLocations.length; i++) { const exit = exitLocations[i]; const y = 32 + i * lineHeight; // Draw number circle const circleX = padding + 10; const circleY = y + 9; ctx.fillStyle = '#7cfc00'; ctx.shadowColor = '#7cfc00'; ctx.shadowBlur = 3; ctx.beginPath(); ctx.arc(circleX, circleY, 8, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; ctx.strokeStyle = '#2a4a00'; ctx.lineWidth = 1; ctx.stroke(); // Draw number ctx.fillStyle = '#1a0a00'; ctx.font = 'bold 10px GameFont'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(exit.number.toString(), circleX, circleY); ctx.textAlign = 'left'; // Draw destination name (full name, no truncation) bitmap.drawText(exit.mapName, padding + 25, y, panelWidth - padding - 30, 18, 'left'); } // Position panel to the right of the map this._exitPanelOffset = 20; // Gap between map and panel this._exitDestinationsPanel.x = this._minimapOffsetX + mapPixelWidth * this._minimapScale + this._exitPanelOffset; this._exitDestinationsPanel.y = this._minimapOffsetY; this.addChild(this._exitDestinationsPanel); }; // Keep old function for compatibility but redirect to new one Scene_Map.prototype.drawExitLegend = function() { // Now handled by createExitDestinationsPanel }; Scene_Map.prototype.createExitMarkers = function() { this._exitMarkers = []; for (const exit of exitLocations) { const marker = new Sprite(); marker.bitmap = new Bitmap(24, 24); const ctx = marker.bitmap.context; const cx = 12, cy = 12; // Draw green circle ctx.fillStyle = '#7cfc00'; ctx.shadowColor = '#7cfc00'; ctx.shadowBlur = 4; ctx.beginPath(); ctx.arc(cx, cy, 10, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; ctx.strokeStyle = '#2a4a00'; ctx.lineWidth = 2; ctx.stroke(); // Draw number ctx.fillStyle = '#1a0a00'; ctx.font = 'bold 12px GameFont'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(exit.number.toString(), cx, cy); marker.anchor.x = 0.5; marker.anchor.y = 0.5; // Store exit data on marker for positioning marker._exitData = exit; this._exitMarkers.push(marker); this.addChild(marker); } this.updateExitMarkerPositions(); }; Scene_Map.prototype.updateExitMarkerPositions = function() { if (!this._exitMarkers) return; const offsetX = this._currentOffsetX || this._minimapOffsetX; const offsetY = this._currentOffsetY || this._minimapOffsetY; const scale = this._currentScale || this._minimapScale; for (const marker of this._exitMarkers) { const exit = marker._exitData; const markerX = offsetX + (exit.centerX + 0.5) * this._minimapTileWidth * scale; const markerY = offsetY + (exit.centerY + 0.5) * this._minimapTileHeight * scale; marker.x = markerX; marker.y = markerY; marker.scale.x = Math.max(0.5, minimapZoom * 0.8); marker.scale.y = Math.max(0.5, minimapZoom * 0.8); } }; 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 (except transfers - those are drawn as numbered markers separately) const revealAllMarkers = ConfigManager.minimapRevealAll; const fogOfWarEnabled = ConfigManager.minimapFogOfWar; const mapId = $gameMap.mapId(); for (const event of $gameMap.events()) { if (!event) continue; const isExplored = $gameSystem.isTileExplored(mapId, event.x, event.y); const isEnemy = this.isEnemyEvent(event); // Enemies: visible if fog of war is off OR tile is explored // Other markers: visible if revealAll is on OR tile is explored if (isEnemy) { if (fogOfWarEnabled && !isExplored) continue; } else { if (!revealAllMarkers && !isExplored) 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)) { // Skip - transfers are drawn as numbered markers by createExitMarkers() } else if (this.isEnemyEvent(event)) { // Draw enemy marker (blood red) ctx.fillStyle = '#dc143c'; ctx.shadowColor = '#dc143c'; ctx.shadowBlur = 4; ctx.beginPath(); ctx.arc(ex, ey, markerSize, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; ctx.strokeStyle = '#4a0a0a'; 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(); } } // Draw fog of war over unexplored areas (if enabled) if (ConfigManager.minimapFogOfWar) { this.drawFogOfWar(bitmap, mapWidth, mapHeight, tileWidth, tileHeight); } }; Scene_Map.prototype.drawFogOfWar = function(bitmap, mapWidth, mapHeight, tileWidth, tileHeight) { const ctx = bitmap.context; const mapId = $gameMap.mapId(); for (let y = 0; y < mapHeight; y++) { for (let x = 0; x < mapWidth; x++) { if (!$gameSystem.isTileExplored(mapId, x, y)) { const px = x * tileWidth; const py = y * tileHeight; // Draw dark fog ctx.fillStyle = '#0a0806'; ctx.fillRect(px, py, tileWidth, tileHeight); // Add some texture/noise to the fog if ((x + y) % 3 === 0) { ctx.fillStyle = 'rgba(20, 15, 10, 0.5)'; ctx.fillRect(px, py, tileWidth, tileHeight); } } else { // Check for partially explored edges (adjacent to unexplored) const hasUnexploredNeighbor = (x > 0 && !$gameSystem.isTileExplored(mapId, x - 1, y)) || (x < mapWidth - 1 && !$gameSystem.isTileExplored(mapId, x + 1, y)) || (y > 0 && !$gameSystem.isTileExplored(mapId, x, y - 1)) || (y < mapHeight - 1 && !$gameSystem.isTileExplored(mapId, x, y + 1)); if (hasUnexploredNeighbor) { // Draw subtle edge shadow const px = x * tileWidth; const py = y * tileHeight; ctx.fillStyle = 'rgba(10, 8, 6, 0.3)'; ctx.fillRect(px, py, tileWidth, tileHeight); } } } } }; // 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); }; // Check if an event is an enemy (triggers battle) Scene_Map.prototype.isEnemyEvent = function(event) { if (!event) return false; // Get the current active page - if no page is active, event is not visible const page = event.page(); if (!page) return false; // Must have a character graphic on the current page (enemy is visible) if (!page.image || !page.image.characterName) return false; // Get the event data const eventData = event.event(); if (!eventData) return false; // Check for tag in the event note (this game's primary enemy marker) const note = eventData.note || ''; if (note.includes('')) return true; // Check event name for specific enemy patterns used in this game const name = eventData.name; if (name.includes('エネミーシンボル')) return true; // "Enemy Symbol" if (name.includes('ボス') && !name.includes('移動')) return true; // "Boss" but not transfer events // Check the current page for Battle Processing command (code 301) const list = page.list; if (!list) return false; for (const cmd of list) { if (cmd.code === 301) { // Battle Processing command return true; } } return false; }; // 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; // Use current transform offsets for zoom/pan support const offsetX = this._currentOffsetX || this._minimapOffsetX; const offsetY = this._currentOffsetY || this._minimapOffsetY; const scale = this._currentScale || this._minimapScale; // Calculate marker position const markerX = offsetX + (playerX + 0.5) * this._minimapTileWidth * scale; const markerY = offsetY + (playerY + 0.5) * this._minimapTileHeight * scale; this._minimapPlayerMarker.x = markerX; this._minimapPlayerMarker.y = markerY; // Pulse effect with zoom scaling const pulse = Math.sin(Graphics.frameCount * 0.1) * 0.2 + 1; const zoomScale = Math.max(0.5, minimapZoom * 0.8); this._minimapPlayerMarker.scale.x = pulse * zoomScale; this._minimapPlayerMarker.scale.y = pulse * zoomScale; }; Scene_Map.prototype.removeMinimapOverlay = function() { if (this._minimapOverlay) { this.removeChild(this._minimapOverlay); this._minimapOverlay = null; } if (this._minimapBorder) { this.removeChild(this._minimapBorder); this._minimapBorder = null; } if (this._minimapContent) { this.removeChild(this._minimapContent); this._minimapContent = null; } if (this._minimapPlayerMarker) { this.removeChild(this._minimapPlayerMarker); this._minimapPlayerMarker = null; } if (this._exitMarkers) { for (const marker of this._exitMarkers) { this.removeChild(marker); } this._exitMarkers = null; } if (this._exitDestinationsPanel) { this.removeChild(this._exitDestinationsPanel); this._exitDestinationsPanel = null; } if (this._legendPanel) { this.removeChild(this._legendPanel); this._legendPanel = null; } if (this._titlePanel) { this.removeChild(this._titlePanel); this._titlePanel = null; } if (this._controlsPanel) { this.removeChild(this._controlsPanel); this._controlsPanel = 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; }; })();