princess-synergy/www/js/plugins/DazedDataReload.js

330 lines
13 KiB
JavaScript

/*:
* @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);
};
})();