diff --git a/data/System.json b/data/System.json index 26b1fca..24628ad 100644 --- a/data/System.json +++ b/data/System.json @@ -149,7 +149,7 @@ "", "Ability" ], - "gameTitle": "Unholy Maiden (Press M for Minimap) | TL: DazedAnon & O&M", + "gameTitle": "Unholy Maiden | TL: DazedAnon & O&M", "gameoverMe": { "name": "", "pan": 0, diff --git a/js/plugins/EventLabel.js b/js/plugins/EventLabel.js index 19d62ed..ee487ce 100644 --- a/js/plugins/EventLabel.js +++ b/js/plugins/EventLabel.js @@ -280,7 +280,7 @@ Game_Event.prototype.initialize = function() { _Game_Event_initialize.apply(this, arguments); this._labelText = this.findLabelName(); - this._labelSize = param.fontSize || 16; + this._labelSize = 13; this._labelX = PluginManagerEx.findMetaValue(this.event(), 'LB_X') || 0; this._labelY = PluginManagerEx.findMetaValue(this.event(), 'LB_Y') || 0; this._labelZ = PluginManagerEx.findMetaValue(this.event(), 'LB_Z') || 0; diff --git a/js/plugins/MinimapZoom.js b/js/plugins/MinimapZoom.js index dc7ead8..6ded3ff 100644 --- a/js/plugins/MinimapZoom.js +++ b/js/plugins/MinimapZoom.js @@ -3,7 +3,7 @@ //============================================================================= /*: - * @plugindesc Adds a minimap/zoom-out feature. Press M to toggle a zoomed-out view of the entire map. + * @plugindesc Adds a minimap/zoom-out feature with exit labels, zoom and pan controls. * @author Dazed Translations * * @param Toggle Key @@ -14,7 +14,30 @@ * 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. + * + * 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() { @@ -28,6 +51,28 @@ // 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; @@ -43,10 +88,53 @@ }; //========================================================================= - // Input - Add minimap key + // 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 + Input.keyMapper[16] = 'minimapZoomIn'; // Shift key + Input.keyMapper[17] = 'minimapZoomOut'; // Ctrl key //========================================================================= // Auto-close minimap when any event starts @@ -60,6 +148,211 @@ _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 //========================================================================= @@ -71,6 +364,16 @@ }; 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) { @@ -91,6 +394,9 @@ // 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(); } @@ -98,6 +404,275 @@ } }; + 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 + if (Input.isTriggered('minimapZoomIn') || Input.isRepeated('minimapZoomIn')) { + minimapZoom = Math.min(MAX_ZOOM, minimapZoom + ZOOM_STEP); + zoomChanged = true; + } + if (Input.isTriggered('minimapZoomOut') || Input.isRepeated('minimapZoomOut')) { + 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) { @@ -122,6 +697,15 @@ 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; @@ -191,36 +775,36 @@ // 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(); + // 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() { @@ -252,9 +836,21 @@ ctx.fillRect(0, 0, w, h); }; - Scene_Map.prototype.drawOrnateBorder = function(x, y, width, height) { - const ctx = this._minimapOverlay.bitmap.context; + 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'; @@ -310,71 +906,374 @@ 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.drawMinimapLegend = function() { - const bitmap = this._minimapOverlay.bitmap; - const legendX = 18; - const legendY = 50; - const dotSize = 10; - const lineHeight = 24; + 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; - bitmap.fontSize = 14; + // 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; - // Legend title with underline - bitmap.drawText('Legend', legendX, legendY, 100, 18, 'left'); + // 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(legendX, legendY + 18); - ctx.lineTo(legendX + 55, legendY + 18); + 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, legendY + lineHeight + dotSize/2 + 5, dotSize/2, 0, Math.PI * 2); + 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 + 10, legendY + lineHeight + 5, 100, 18, 'left'); + 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, legendY + lineHeight * 2 + dotSize/2 + 5, dotSize/2, 0, Math.PI * 2); + 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 + 10, legendY + lineHeight * 2 + 5, 100, 18, 'left'); + 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, legendY + lineHeight * 3 + dotSize/2 + 5, dotSize/2, 0, Math.PI * 2); + 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 + 10, legendY + lineHeight * 3 + 5, 150, 18, 'left'); + 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, legendY + lineHeight * 4 + dotSize/2 + 5, dotSize/2, 0, Math.PI * 2); + 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 + 10, legendY + lineHeight * 4 + 5, 150, 18, 'left'); + 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() { @@ -520,10 +1419,25 @@ } } - // Draw important events as markers + // 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; @@ -543,15 +1457,17 @@ 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; + // 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 = '#2a4a00'; + ctx.strokeStyle = '#4a0a0a'; ctx.lineWidth = 2; ctx.stroke(); } else if (this.isImportantEvent(event)) { @@ -568,6 +1484,50 @@ 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 @@ -629,6 +1589,43 @@ 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; @@ -828,17 +1825,23 @@ 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 = this._minimapOffsetX + (playerX + 0.5) * this._minimapTileWidth * this._minimapScale; - const markerY = this._minimapOffsetY + (playerY + 0.5) * this._minimapTileHeight * this._minimapScale; + 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 + // Pulse effect with zoom scaling const pulse = Math.sin(Graphics.frameCount * 0.1) * 0.2 + 1; - this._minimapPlayerMarker.scale.x = pulse; - this._minimapPlayerMarker.scale.y = pulse; + 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() { @@ -846,6 +1849,10 @@ 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; @@ -854,6 +1861,28 @@ 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; + } }; //=========================================================================