diff --git a/.gitignore b/.gitignore index 428d58db..29d7c761 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ game/**/* todo.md corescript/**/* corescript.zip +package-lock.json diff --git a/js/rpg_core/Graphics.js b/js/rpg_core/Graphics.js index 93a252a0..cd5b5429 100644 --- a/js/rpg_core/Graphics.js +++ b/js/rpg_core/Graphics.js @@ -360,6 +360,11 @@ Graphics.printLoadingError = function(url) { if (this._errorPrinter && !this._errorShowed) { this._updateErrorPrinter(); this._errorPrinter.innerHTML = this._makeErrorHtml('Loading Error', 'Failed to load: ' + url); + this._errorPrinter.style.userSelect = 'text'; + this._errorPrinter.style.webkitUserSelect = 'text'; + this._errorPrinter.style.msUserSelect = 'text'; + this._errorPrinter.style.mozUserSelect = 'text'; + this._errorPrinter.oncontextmenu = null; // enable context menu var button = document.createElement('button'); button.innerHTML = 'Retry'; button.style.fontSize = '24px'; @@ -385,6 +390,11 @@ Graphics.printLoadingError = function(url) { Graphics.eraseLoadingError = function() { if (this._errorPrinter && !this._errorShowed) { this._errorPrinter.innerHTML = ''; + this._errorPrinter.style.userSelect = 'none'; + this._errorPrinter.style.webkitUserSelect = 'none'; + this._errorPrinter.style.msUserSelect = 'none'; + this._errorPrinter.style.mozUserSelect = 'none'; + this._errorPrinter.oncontextmenu = function() { return false; }; this.startLoading(); } }; @@ -405,27 +415,32 @@ Graphics.printError = function(name, message) { if (this._errorPrinter) { this._updateErrorPrinter(); this._errorPrinter.innerHTML = this._makeErrorHtml(name, message); - this._makeErrorMessage(); + this._errorPrinter.style.userSelect = 'text'; + this._errorPrinter.style.webkitUserSelect = 'text'; + this._errorPrinter.style.msUserSelect = 'text'; + this._errorPrinter.style.mozUserSelect = 'text'; + this._errorPrinter.oncontextmenu = null; // enable context menu + if (this._errorMessage) { + this._makeErrorMessage(); + } } this._applyCanvasFilter(); this._clearUpperCanvas(); }; /** - * Shows the stacktrace of error. + * Shows the detail of error. * * @static - * @method printStackTrace + * @method printErrorDetail */ -Graphics.printStackTrace = function(stack) { - if (this._errorPrinter) { - stack = (stack || '') - .replace(/file:.*js\//g, '') - .replace(/http:.*js\//g, '') - .replace(/https:.*js\//g, '') - .replace(/chrome-extension:.*js\//g, '') - .replace(/\n/g, '
'); - this._makeStackTrace(decodeURIComponent(stack)); +Graphics.printErrorDetail = function(error) { + if (this._errorPrinter && this._showErrorDetail) { + var eventInfo = this._formatEventInfo(error); + var eventCommandInfo = this._formatEventCommandInfo(error); + var info = eventCommandInfo ? eventInfo + ", " + eventCommandInfo : eventInfo; + var stack = this._formatStackTrace(error); + this._makeErrorDetail(info, stack); } }; @@ -439,6 +454,16 @@ Graphics.setErrorMessage = function(message) { this._errorMessage = message; }; +/** + * Sets whether shows the detail of error. + * + * @static + * @method setShowErrorDetail + */ +Graphics.setShowErrorDetail = function(showErrorDetail) { + this._showErrorDetail = showErrorDetail; +}; + /** * Shows the FPSMeter element. * @@ -862,16 +887,17 @@ Graphics._createErrorPrinter = function() { */ Graphics._updateErrorPrinter = function() { this._errorPrinter.width = this._width * 0.9; - this._errorPrinter.height = this._errorShowed ? this._height * 0.9 : 40; + if (this._errorShowed && this._showErrorDetail) { + this._errorPrinter.height = this._height * 0.9; + } else if (this._errorShowed && this._errorMessage) { + this._errorPrinter.height = 100; + } else { + this._errorPrinter.height = 40; + } this._errorPrinter.style.textAlign = 'center'; this._errorPrinter.style.textShadow = '1px 1px 3px #000'; this._errorPrinter.style.fontSize = '20px'; this._errorPrinter.style.zIndex = 99; - this._errorPrinter.style.userSelect = 'text'; - this._errorPrinter.style.webkitUserSelect = 'text'; - this._errorPrinter.style.msUserSelect = 'text'; - this._errorPrinter.style.mozUserSelect = 'text'; - this._errorPrinter.oncontextmenu = null; // enable context menu this._centerElement(this._errorPrinter); }; @@ -886,23 +912,82 @@ Graphics._makeErrorMessage = function() { style.color = 'white'; style.textAlign = 'left'; style.fontSize = '18px'; - mainMessage.innerHTML = '
' + (this._errorMessage || ''); + mainMessage.innerHTML = '
' + this._errorMessage; this._errorPrinter.appendChild(mainMessage); }; /** * @static - * @method _makeStackTrace + * @method _makeErrorDetail * @private */ -Graphics._makeStackTrace = function(stack) { - var stackTrace = document.createElement('div'); - var style = stackTrace.style; +Graphics._makeErrorDetail = function(info, stack) { + var detail = document.createElement('div'); + var style = detail.style; style.color = 'white'; style.textAlign = 'left'; style.fontSize = '18px'; - stackTrace.innerHTML = '

' + stack + '
'; - this._errorPrinter.appendChild(stackTrace); + detail.innerHTML = '

' + info + '

' + stack; + this._errorPrinter.appendChild(detail); +}; + +/** + * @static + * @method _formatEventInfo + * @private + */ +Graphics._formatEventInfo = function(error) { + switch (String(error.eventType)) { + case "map_event": + return "MapID: %1, MapEventID: %2, page: %3, line: %4".format(error.mapId, error.mapEventId, error.page, error.line); + case "common_event": + return "CommonEventID: %1, line: %2".format(error.commonEventId, error.line); + case "battle_event": + return "TroopID: %1, page: %2, line: %3".format(error.troopId, error.page, error.line); + case "test_event": + return "TestEvent, line: %1".format(error.line); + default: + return "No information"; + } +}; + +/** + * @static + * @method _formatEventCommandInfo + * @private + */ +Graphics._formatEventCommandInfo = function(error) { + switch (String(error.eventCommand)) { + case "plugin_command": + return "◆Plugin Command: " + error.content; + case "script": + return "◆Script: " + error.content; + case "control_variables": + return "◆Control Variables: Script: " + error.content; + case "conditional_branch_script": + return "◆If: Script: " + error.content; + case "set_route_script": + return "◆Set Movement Route: ◇Script: " + error.content; + case "auto_route_script": + return "Autonomous Movement Custom Route: ◇Script: " + error.content; + case "other": + default: + return ""; + } +}; + +/** + * @static + * @method _formatStackTrace + * @private + */ +Graphics._formatStackTrace = function(error) { + return decodeURIComponent((error.stack || '') + .replace(/file:.*js\//g, '') + .replace(/http:.*js\//g, '') + .replace(/https:.*js\//g, '') + .replace(/chrome-extension:.*js\//g, '') + .replace(/\n/g, '
')); }; /** diff --git a/js/rpg_managers/SceneManager.js b/js/rpg_managers/SceneManager.js index d4c8d804..0463187e 100644 --- a/js/rpg_managers/SceneManager.js +++ b/js/rpg_managers/SceneManager.js @@ -200,7 +200,7 @@ SceneManager.onKeyDown = function(event) { SceneManager.catchException = function(e) { if (e instanceof Error) { Graphics.printError(e.name, e.message); - Graphics.printStackTrace(e.stack); + Graphics.printErrorDetail(e); console.error(e.stack); } else { Graphics.printError('UnknownError', e); diff --git a/js/rpg_objects/Game_Character.js b/js/rpg_objects/Game_Character.js index 85bfed0d..ae731ead 100644 --- a/js/rpg_objects/Game_Character.js +++ b/js/rpg_objects/Game_Character.js @@ -69,6 +69,7 @@ Game_Character.prototype.initMembers = function() { this._originalMoveRoute = null; this._originalMoveRouteIndex = 0; this._waitCount = 0; + this._callerEventInfo = null; }; Game_Character.prototype.memorizeMoveRoute = function() { @@ -80,6 +81,7 @@ Game_Character.prototype.restoreMoveRoute = function() { this._moveRoute = this._originalMoveRoute; this._moveRouteIndex = this._originalMoveRouteIndex; this._originalMoveRoute = null; + this._callerEventInfo = null; }; Game_Character.prototype.isMoveRouteForcing = function() { @@ -102,6 +104,10 @@ Game_Character.prototype.forceMoveRoute = function(moveRoute) { this._waitCount = 0; }; +Game_Character.prototype.setCallerEventInfo = function(callerEventInfo) { + this._callerEventInfo = callerEventInfo; +}; + Game_Character.prototype.updateStop = function() { Game_CharacterBase.prototype.updateStop.call(this); if (this._moveRouteForcing) { @@ -262,7 +268,27 @@ Game_Character.prototype.processMoveCommand = function(command) { AudioManager.playSe(params[0]); break; case gc.ROUTE_SCRIPT: - eval(params[0]); + try { + eval(params[0]); + } catch (error) { + if (this._callerEventInfo) { + for (var key in this._callerEventInfo) { + error[key] = this._callerEventInfo[key]; + } + error.line += this._moveRouteIndex + 1; + error.eventCommand = "set_route_script"; + error.content = command.parameters[0]; + } else { + error.eventType = "map_event"; + error.mapId = this._mapId; + error.mapEventId = this._eventId; + error.page = this._pageIndex + 1; + error.line = this._moveRouteIndex + 1; + error.eventCommand = "auto_route_script"; + error.content = command.parameters[0]; + } + throw error; + } break; } }; diff --git a/js/rpg_objects/Game_CommonEvent.js b/js/rpg_objects/Game_CommonEvent.js index 5a9a707c..611152a7 100644 --- a/js/rpg_objects/Game_CommonEvent.js +++ b/js/rpg_objects/Game_CommonEvent.js @@ -40,6 +40,7 @@ Game_CommonEvent.prototype.update = function() { if (this._interpreter) { if (!this._interpreter.isRunning()) { this._interpreter.setup(this.list()); + this._interpreter.setEventInfo({ eventType: 'common_event', commonEventId: this._commonEventId }); } this._interpreter.update(); } diff --git a/js/rpg_objects/Game_Event.js b/js/rpg_objects/Game_Event.js index e1924ba7..cf570c2c 100644 --- a/js/rpg_objects/Game_Event.js +++ b/js/rpg_objects/Game_Event.js @@ -322,6 +322,7 @@ Game_Event.prototype.updateParallel = function() { if (this._interpreter) { if (!this._interpreter.isRunning()) { this._interpreter.setup(this.list(), this._eventId); + this._interpreter.setEventInfo(this.getEventInfo()); } this._interpreter.update(); } @@ -336,3 +337,7 @@ Game_Event.prototype.forceMoveRoute = function(moveRoute) { Game_Character.prototype.forceMoveRoute.call(this, moveRoute); this._prelockDirection = 0; }; + +Game_Event.prototype.getEventInfo = function() { + return { eventType: "map_event", mapId: this._mapId, mapEventId: this._eventId, page: this._pageIndex + 1 }; +}; diff --git a/js/rpg_objects/Game_Interpreter.js b/js/rpg_objects/Game_Interpreter.js index 56b36b83..33d709bc 100644 --- a/js/rpg_objects/Game_Interpreter.js +++ b/js/rpg_objects/Game_Interpreter.js @@ -32,6 +32,7 @@ Game_Interpreter.prototype.clear = function() { this._waitCount = 0; this._waitMode = ''; this._comments = ''; + this._eventInfo = null; this._character = null; this._childInterpreter = null; }; @@ -52,9 +53,14 @@ Game_Interpreter.prototype.isOnCurrentMap = function() { return this._mapId === $gameMap.mapId(); }; +Game_Interpreter.prototype.setEventInfo = function(eventInfo) { + this._eventInfo = eventInfo; +}; + Game_Interpreter.prototype.setupReservedCommonEvent = function() { if ($gameTemp.isCommonEventReserved()) { this.setup($gameTemp.reservedCommonEvent().list); + this.setEventInfo({ eventType: 'common_event', commonEventId: $gameTemp.reservedCommonEventId() }); $gameTemp.clearCommonEvent(); return true; } else { @@ -166,8 +172,17 @@ Game_Interpreter.prototype.executeCommand = function() { this._indent = command.indent; var methodName = 'command' + command.code; if (typeof this[methodName] === 'function') { - if (!this[methodName]()) { - return false; + try { + if (!this[methodName]()) { + return false; + } + } catch (error) { + for (var key in this._eventInfo) { + error[key] = this._eventInfo[key]; + } + error.eventCommand = error.eventCommand || "other"; + error.line = error.line || this._index + 1; + throw error; } } this._index++; @@ -543,7 +558,13 @@ Game_Interpreter.prototype.command111 = function() { result = Input.isPressed(this._params[1]); break; case 12: // Script - result = !!eval(this._params[1]); + try { + result = !!eval(this._params[1]); + } catch (error) { + error.eventCommand = "conditional_branch_script"; + error.content = this._params[1]; + throw error; + } break; case 13: // Vehicle result = ($gamePlayer.vehicle() === $gameMap.vehicle(this._params[1])); @@ -616,6 +637,7 @@ Game_Interpreter.prototype.command117 = function() { Game_Interpreter.prototype.setupChild = function(list, eventId) { this._childInterpreter = new Game_Interpreter(this._depth + 1); this._childInterpreter.setup(list, eventId); + this._childInterpreter.setEventInfo({ eventType: 'common_event', commonEventId: this._params[0] }); }; // Label @@ -680,7 +702,13 @@ Game_Interpreter.prototype.command122 = function() { value = this.gameDataOperand(this._params[4], this._params[5], this._params[6]); break; case 4: // Script - value = eval(this._params[4]); + try { + value = eval(this._params[4]); + } catch (error) { + error.eventCommand = "control_variables"; + error.content = this._params[4]; + throw error; + } break; } for (var i = this._params[0]; i <= this._params[1]; i++) { @@ -1024,6 +1052,9 @@ Game_Interpreter.prototype.command205 = function() { this._character = this.character(this._params[0]); if (this._character) { this._character.forceMoveRoute(this._params[1]); + var eventInfo = JsonEx.makeDeepCopy(this._eventInfo); + eventInfo.line = this._index + 1; + this._character.setCallerEventInfo(eventInfo); if (this._params[1].wait) { this.setWaitMode('route'); } @@ -1731,12 +1762,21 @@ Game_Interpreter.prototype.command354 = function() { // Script Game_Interpreter.prototype.command355 = function() { + var startLine = this._index + 1; var script = this.currentCommand().parameters[0] + '\n'; while (this.nextEventCode() === 655) { this._index++; script += this.currentCommand().parameters[0] + '\n'; } - eval(script); + var endLine = this._index + 1; + try { + eval(script); + } catch (error) { + error.line = startLine + "-" + endLine; + error.eventCommand = "script"; + error.content = script; + throw error; + } return true; }; @@ -1744,7 +1784,13 @@ Game_Interpreter.prototype.command355 = function() { Game_Interpreter.prototype.command356 = function() { var args = this._params[0].split(" "); var command = args.shift(); - this.pluginCommand(command, args); + try { + this.pluginCommand(command, args); + } catch (error) { + error.eventCommand = "plugin_command"; + error.content = this._params[0]; + throw error; + } return true; }; diff --git a/js/rpg_objects/Game_Map.js b/js/rpg_objects/Game_Map.js index 10352822..fb6ed9c6 100644 --- a/js/rpg_objects/Game_Map.js +++ b/js/rpg_objects/Game_Map.js @@ -756,6 +756,7 @@ Game_Map.prototype.setupStartingEvent = function() { Game_Map.prototype.setupTestEvent = function() { if ($testEvent) { this._interpreter.setup($testEvent, 0); + this._interpreter.setEventInfo({ eventType: 'test_event' }); $testEvent = null; return true; } @@ -769,6 +770,7 @@ Game_Map.prototype.setupStartingMapEvent = function() { if (event.isStarting()) { event.clearStartingFlag(); this._interpreter.setup(event.list(), event.eventId()); + this._interpreter.setEventInfo(event.getEventInfo()); return true; } } @@ -780,6 +782,7 @@ Game_Map.prototype.setupAutorunCommonEvent = function() { var event = $dataCommonEvents[i]; if (event && event.trigger === 1 && $gameSwitches.value(event.switchId)) { this._interpreter.setup(event.list); + this._interpreter.setEventInfo({ eventType: 'common_event', commonEventId: i }); return true; } } diff --git a/js/rpg_objects/Game_Temp.js b/js/rpg_objects/Game_Temp.js index a3c59be1..db3b1fc0 100644 --- a/js/rpg_objects/Game_Temp.js +++ b/js/rpg_objects/Game_Temp.js @@ -34,6 +34,10 @@ Game_Temp.prototype.reservedCommonEvent = function() { return $dataCommonEvents[this._commonEventId]; }; +Game_Temp.prototype.reservedCommonEventId = function() { + return this._commonEventId; +}; + Game_Temp.prototype.setDestination = function(x, y) { this._destinationX = x; this._destinationY = y; diff --git a/js/rpg_objects/Game_Troop.js b/js/rpg_objects/Game_Troop.js index fb520c5c..4be2c76c 100644 --- a/js/rpg_objects/Game_Troop.js +++ b/js/rpg_objects/Game_Troop.js @@ -159,6 +159,7 @@ Game_Troop.prototype.setupBattleEvent = function() { var page = pages[i]; if (this.meetsConditions(page) && !this._eventFlags[i]) { this._interpreter.setup(page.list); + this._interpreter.setEventInfo({ eventType: 'battle_event', troopId: this._troopId, page: i + 1 }); if (page.span <= 1) { this._eventFlags[i] = true; } diff --git a/package.json b/package.json index b07040f6..1d1a1fb9 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,11 @@ "build:windows": "node ./concat.js rpg_windows", "watch": "node ./watch.js", "build": "run-p build:*", - "copy:corescripts": "cpx ./dist/*.js ./game/js/", - "copy:libs": "cpx ./js/libs/*.js ./game/js/libs/", - "copy:main": "cpx ./js/main.js ./game/js/", - "copy:plugins": "cpx ./plugins/*.js ./game/js/plugins/", - "copy:template": "cpx ./template/**/* ./game/", + "copy:corescripts": "cpx './dist/*.js' ./game/js/", + "copy:libs": "cpx './js/libs/*.js' ./game/js/libs/", + "copy:main": "cpx './js/main.js' ./game/js/", + "copy:plugins": "cpx './plugins/*.js' ./game/js/plugins/", + "copy:template": "cpx './template/**/*' ./game/", "copy": "run-p copy:*", "copy-all": "node copy-all.js ./corescript", "zip": "bestzip corescript.zip ./corescript/", diff --git a/plugins/Community_Basic.js b/plugins/Community_Basic.js index 0a05828f..be9ed186 100644 --- a/plugins/Community_Basic.js +++ b/plugins/Community_Basic.js @@ -64,6 +64,11 @@ * @desc The message when error occurred * @default Error occurred. Please ask to the creator of this game. * + * @param showErrorDetail + * @type boolean + * @desc Show where the error is caused and stack trace when error + * @default true + * * @param enableProgressBar * @type boolean * @desc Show progress bar when it takes a long time to load resources @@ -144,8 +149,15 @@ * @param errorMessage * @type string * @text エラーメッセージ + * @desc エラー時にプレイヤーに向けて表示するメッセージです * @default エラーが発生しました。ゲームの作者にご連絡ください。 * + * @param showErrorDetail + * @type boolean + * @text エラー詳細表示 + * @desc ONにすると、エラー時にエラーを発生させたイベントの情報とスタックトレースを表示します + * @default true + * * @param enableProgressBar * @type boolean * @text ロード進捗バー有効化 @@ -181,6 +193,7 @@ var maxRenderingFps = toNumber(parameters['maxRenderingFps'], 0); var autoSaveFileId = toNumber(parameters['autoSaveFileId'], 0); var errorMessage = parameters['errorMessage']; + var showErrorDetail = parameters['showErrorDetail'] === 'true'; var enableProgressBar = parameters['enableProgressBar'] === 'true'; var windowWidth; @@ -263,5 +276,6 @@ DataManager.setAutoSaveFileId(autoSaveFileId); Graphics.setErrorMessage(errorMessage); + Graphics.setShowErrorDetail(showErrorDetail); Graphics.setProgressEnabled(enableProgressBar); })(); \ No newline at end of file