/*: * @plugindesc [Dazed Translations] Hot-reload data files and refresh cached save data. Press F9 in-game. * @author DazedTranslations * * @help * Press F9 while in-game to: * 1. Reload Nupu_PSData.json, RdData.json, and Nupu_GameSetting.json from disk * 2. Refresh all mail text in $gameSystem.AllMailList from updated PSData * 3. Reset the MemoryDatas cache so it rebuilds from updated PSData * 4. Scan all Map/CommonEvent data for AddAddress calls and update saved addresses * 5. Show a confirmation message on screen * * Console utilities (open dev console with F12): * DazedClearComments() - Clears old comments from save (they'll re-add in English as you play) * DazedClearBBS() - Clears old BBS posts from save * * This allows translation changes to take effect on existing saves * without starting a new game or restarting the application. */ (function() { 'use strict'; var RELOAD_KEY = 120; // F9 // ========================================================================= // 1. Reload a JSON data file from disk (synchronous for reliability) // ========================================================================= function reloadDataFile(globalName, src) { var xhr = new XMLHttpRequest(); var url = 'data/' + src; xhr.open('GET', url, false); // synchronous xhr.overrideMimeType('application/json'); xhr.send(); if (xhr.status < 400) { window[globalName] = JSON.parse(xhr.responseText); DataManager.onLoad(window[globalName]); return true; } return false; } // ========================================================================= // 2. Refresh mail text in save data from updated PSData // ========================================================================= function refreshMailData() { if (!$gameSystem || !$gameSystem.AllMailList) return 0; if (!$Nupu_PSData || !$Nupu_PSData.MailDatas) return 0; var updated = 0; for (var i = 0; i < $gameSystem.AllMailList.length; i++) { var savedMail = $gameSystem.AllMailList[i]; if (savedMail.ToolNo == null || savedMail.ToolNo < 0) continue; // Find matching PSData entry by ToolNo for (var j = 0; j < $Nupu_PSData.MailDatas.length; j++) { var srcMail = $Nupu_PSData.MailDatas[j]; if (srcMail.No === savedMail.ToolNo) { savedMail.ChrStr = srcMail.Name; savedMail.MTitle = srcMail.Title; savedMail.MNaiyo = srcMail.MailText; if (srcMail.PicDatas && savedMail.PicData) { for (var k = 0; k < srcMail.PicDatas.length && k < savedMail.PicData.length; k++) { savedMail.PicData[k].PicDesc = srcMail.PicDatas[k].PicDesc; } } updated++; break; } } } return updated; } // ========================================================================= // 3. Reset MemoryDatas cache so it rebuilds from fresh PSData // ========================================================================= function resetMemoryCache() { if (typeof MemoryDatas !== 'undefined') { MemoryDatas = null; } } // ========================================================================= // 4. Scan event data for AddAddress calls and refresh saved addresses // ========================================================================= function padZero(n, digits) { var s = String(n); while (s.length < digits) s = '0' + s; return s; } function loadFileTextSync(url) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, false); xhr.overrideMimeType('application/json'); try { xhr.send(); if (xhr.status < 400) return xhr.responseText; } catch (e) {} return null; } // Extract script blocks from event command lists (code 355 starts, 655 continues) function getScriptBlocks(list) { var blocks = []; var current = null; for (var i = 0; i < list.length; i++) { var cmd = list[i]; if (cmd.code === 355) { if (current !== null) blocks.push(current); current = cmd.parameters[0]; } else if (cmd.code === 655 && current !== null) { current += '\n' + cmd.parameters[0]; } else { if (current !== null) blocks.push(current); current = null; } } if (current !== null) blocks.push(current); return blocks; } // Extract AddAddress calls from script blocks function extractAddressFromScripts(scripts, addressMap) { for (var i = 0; i < scripts.length; i++) { var match = scripts[i].match(/AddAddress\(\s*(\d+)\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"/); if (match) { var viewNo = parseInt(match[1]); var name = match[2]; var picName = match[3]; // Key by viewNo + picName to handle slot reuse for different characters var key = viewNo + '|' + picName; addressMap[key] = { viewNo: viewNo, name: name, picName: picName }; } } } // Scan a single event list function scanEventCommands(list, addressMap) { if (!list) return; var scripts = getScriptBlocks(list); extractAddressFromScripts(scripts, addressMap); } // Scan all events in a parsed map function scanMapData(mapData, addressMap) { if (!mapData || !mapData.events) return; for (var i = 0; i < mapData.events.length; i++) { var ev = mapData.events[i]; if (!ev || !ev.pages) continue; for (var j = 0; j < ev.pages.length; j++) { if (ev.pages[j] && ev.pages[j].list) { scanEventCommands(ev.pages[j].list, addressMap); } } } } // Scan CommonEvents function scanCommonEvents(ceData, addressMap) { if (!ceData) return; for (var i = 0; i < ceData.length; i++) { if (ceData[i] && ceData[i].list) { scanEventCommands(ceData[i].list, addressMap); } } } // Main scan: load all map files + CommonEvents and extract AddAddress calls function scanAllAddressCalls() { var addressMap = {}; console.log('[DazedTranslations] Scanning event data for AddAddress calls...'); // Scan CommonEvents (already loaded in memory, but reload fresh from disk) var ceText = loadFileTextSync('data/CommonEvents.json'); if (ceText) { // Quick check before expensive parse if (ceText.indexOf('AddAddress') !== -1) { var ceData = JSON.parse(ceText); scanCommonEvents(ceData, addressMap); } } // Scan all maps if ($dataMapInfos) { for (var i = 1; i < $dataMapInfos.length; i++) { if (!$dataMapInfos[i]) continue; var filename = 'data/Map' + padZero(i, 3) + '.json'; var mapText = loadFileTextSync(filename); if (mapText && mapText.indexOf('AddAddress') !== -1) { try { var mapData = JSON.parse(mapText); scanMapData(mapData, addressMap); } catch (e) { console.log('[DazedTranslations] Failed to parse ' + filename); } } } } var count = Object.keys(addressMap).length; console.log('[DazedTranslations] Found ' + count + ' unique AddAddress entries'); return addressMap; } // Apply address updates to saved data function refreshAddresses(addressMap) { if (!$gameSystem || !$gameSystem.AddressList) return 0; var updated = 0; for (var i = 0; i < $gameSystem.AddressList.length; i++) { var addr = $gameSystem.AddressList[i]; var key = addr._ViewNo + '|' + addr._PicName; if (addressMap[key]) { var newName = addressMap[key].name; if (addr._Name !== newName) { console.log('[DazedTranslations] Address: "' + addr._Name + '" -> "' + newName + '"'); addr._Name = newName; updated++; } } } return updated; } // ========================================================================= // 5. Console utilities for comments/BBS (can't auto-match without IDs) // ========================================================================= window.DazedClearComments = function() { if (!$gameSystem || !$gameSystem.SComeArr) { console.log('[DazedTranslations] No save loaded.'); return; } var count = $gameSystem.SComeArr.length; $gameSystem.SComeArr = []; console.log('[DazedTranslations] Cleared ' + count + ' comments from save.'); console.log('[DazedTranslations] New comments will appear in English as you play.'); }; window.DazedClearBBS = function() { if (!$gameSystem || !$gameSystem.SBbsArr) { console.log('[DazedTranslations] No save loaded.'); return; } var count = $gameSystem.SBbsArr.length; $gameSystem.SBbsArr = []; console.log('[DazedTranslations] Cleared ' + count + ' BBS posts from save.'); }; // ========================================================================= // 6. Show on-screen notification // ========================================================================= function showReloadNotification(stats) { var lines = ['[DazedTranslations] Data reloaded!']; if (stats.mail > 0) lines.push('Mail: ' + stats.mail + ' updated'); if (stats.addresses > 0) lines.push('Addresses: ' + stats.addresses + ' updated'); var msg = lines.join(' | '); console.log(msg); if ($gameSystem && $gameSystem.SComeArr && $gameSystem.SComeArr.length > 0) { console.log('[DazedTranslations] Note: ' + $gameSystem.SComeArr.length + ' comments in save cannot be auto-updated. Use DazedClearComments() in console to clear them.'); } var scene = SceneManager._scene; if (scene) { var sprite = new Sprite(new Bitmap(Graphics.width, 40)); sprite.bitmap.fontSize = 18; sprite.bitmap.textColor = '#00ff88'; sprite.bitmap.outlineColor = '#000000'; sprite.bitmap.outlineWidth = 4; sprite.bitmap.drawText(msg, 10, 5, Graphics.width - 20, 30, 'left'); sprite.y = 10; sprite.opacity = 255; scene.addChild(sprite); var frames = 0; var fadeInterval = setInterval(function() { frames++; if (frames > 180) { sprite.opacity -= 5; if (sprite.opacity <= 0) { scene.removeChild(sprite); clearInterval(fadeInterval); } } }, 1000 / 60); } } // ========================================================================= // 7. Main reload function // ========================================================================= function performDataReload() { console.log('[DazedTranslations] === Starting full data reload ==='); // Reload JSON data files var ok1 = reloadDataFile('$Nupu_PSData', 'Nupu_PSData.json'); var ok2 = reloadDataFile('$RDData', 'RdData.json'); var ok3 = reloadDataFile('$Nupu_GameSetting', 'Nupu_GameSetting.json'); console.log('[DazedTranslations] PSData: ' + (ok1 ? 'OK' : 'FAIL') + ', RDData: ' + (ok2 ? 'OK' : 'FAIL') + ', GameSetting: ' + (ok3 ? 'OK' : 'FAIL')); // Refresh cached data var mailCount = refreshMailData(); resetMemoryCache(); // Scan and refresh addresses var addressMap = scanAllAddressCalls(); var addrCount = refreshAddresses(addressMap); showReloadNotification({ mail: mailCount, addresses: addrCount }); console.log('[DazedTranslations] === Reload complete ==='); } // ========================================================================= // 8. Hook into the key input system // ========================================================================= var _SceneManager_onKeyDown = SceneManager.onKeyDown; SceneManager.onKeyDown = function(event) { if (event.keyCode === RELOAD_KEY) { event.preventDefault(); performDataReload(); return; } _SceneManager_onKeyDown.call(this, event); }; })();